package edu.cmu.ece.ds.droidchat;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Random;
import java.util.Set;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.Keyboard.Key;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;
import edu.cmu.ece.ds.droidchat.message.ChatAck;
import edu.cmu.ece.ds.droidchat.message.ChatSessionMessage;
import edu.cmu.ece.ds.droidchat.message.ClientLoginMessage;
import edu.cmu.ece.ds.droidchat.message.ClientLoginResponseMessage;
import edu.cmu.ece.ds.droidchat.message.ClientPublicKeyMessage;
import edu.cmu.ece.ds.droidchat.message.ClientRegisterMessage;
import edu.cmu.ece.ds.droidchat.message.ClientRegisterResponseMessage;
import edu.cmu.ece.ds.droidchat.message.ClientUpdateLocationMessage;
import edu.cmu.ece.ds.droidchat.message.CreateSessionInfoRequestMessage;
import edu.cmu.ece.ds.droidchat.message.CreateSessionInfoRequestMessageR;
import edu.cmu.ece.ds.droidchat.message.CreateSessionInfoResponseMessage;
import edu.cmu.ece.ds.droidchat.message.CreateSessionInfoResponseMessageR;
import edu.cmu.ece.ds.droidchat.message.CreateSessionInfoResponseMessageR;
import edu.cmu.ece.ds.droidchat.message.CreateSessionInvitationMessage;
import edu.cmu.ece.ds.droidchat.message.CreateSessionInvitationResponseMessage;
import edu.cmu.ece.ds.droidchat.message.CreateSessionKeyMessage;
import edu.cmu.ece.ds.droidchat.message.DroidChatMessage;
import edu.cmu.ece.ds.droidchat.message.EncryptCreateSessionInvitationMessage;
import edu.cmu.ece.ds.droidchat.message.EncryptCreateSessionInvitationResponseMessage;
import edu.cmu.ece.ds.droidchat.message.InvitationAck;
import edu.cmu.ece.ds.droidchat.message.LogOutCommandFromServer;
import edu.cmu.ece.ds.droidchat.message.LogOutRequestFromClient;
import edu.cmu.ece.ds.droidchat.message.NeighborInfoMessage;
import edu.cmu.ece.ds.droidchat.message.ReportKeepAliveMessage;
import edu.cmu.ece.ds.droidchat.message.ReportNeighborMessage;
import edu.cmu.ece.ds.droidchat.message.ResponseAck;
import edu.cmu.ece.ds.droidchat.message.SSLClientDummyMessage;
import edu.cmu.ece.ds.droidchat.model.ChangeRequest;
import edu.cmu.ece.ds.droidchat.model.ChatMessage;
import edu.cmu.ece.ds.droidchat.model.ChatSessionInfo;
import edu.cmu.ece.ds.droidchat.model.ChatSessionInfo.ParticipantLife;
import edu.cmu.ece.ds.droidchat.model.LoginSessionAttribute;
import edu.cmu.ece.ds.droidchat.model.MessageBackgroundColor;
import edu.cmu.ece.ds.droidchat.model.NeighborInfo;
import edu.cmu.ece.ds.droidchat.model.StringConstants;
import edu.cmu.ece.ds.droidchat.model.UserInfo;
import edu.cmu.ece.ds.droidchat.retransmission.ChatSendRecord;
import edu.cmu.ece.ds.droidchat.retransmission.InvitationSendRecord;
import edu.cmu.ece.ds.droidchat.util.Toolbox;


public class NetworkService extends Service {
    private static final boolean DEBUG = true;

    private static final String TAG = "NetworkService";
    private LoginSessionAttribute login_record;
    private String lastTriedUsername = null;
    private String lastTriedPassword = null;
    private boolean tempDoNotUpdate = false;
    private boolean tempIgnoreLogOut = false;
    
    private NioClient client;
    
    // the latest location update round number
    private int updateRoundNumber = 0; 
    private long lastUpdateTime; // the latest location update time
    private long lastCheckTime = 0;
    
    private long timeOut = 5000; // periodic location update time (ms)
    private long ackTimeOut = 400; // retransmission timeout (ms)

    // the latest report neighbor time
    private long lastReportNeigborTime = 0;

    // select() maximum blocking time

    private String[] server_ips;
    private int[] server_ports;
    private int num_servers; // number of servers, set in string.xml
    private int udp_port; // the port number for udp communications
    private boolean use_ssl_with_server = false; // set in string.xml

    // location related parameters
    private Location lastKnownLocation;

    public static final int REAL_MODE = 0;
    public static final int CMU_MODE = 1;
    public static final int WATERFRONT_MODE = 2;
    public static final int LOCATION_A = 3;
    public static final int LOCATION_B = 4;
    public static final int LOCATION_C = 5;
    public static final int LOCATION_D = 6;

    // 5000 forbes ave pittsburgh - CMU Address
    private static final double cmu_latitude = 40.44442;
    private static final double cmu_longitude = -79.94298;

    // 300 Waterfront Drive W Homestead - Waterfront AMC Theater Address
    private static final double waterfront_latitude = 40.40370;
    private static final double waterfront_longitude = -79.91966;

    // location A - Fifth and Maryland
    private static final double A_latitude = 40.45051;
    private static final double A_longitude = -79.92750;

    // location B - Fifth and Negley
    private static final double B_latitude = 40.45110;
    private static final double B_longitude = -79.93326;

    // location C - Fifth and Aiken
    private static final double C_latitude = 40.44842;
    private static final double C_longitude = -79.93420;

    // location D
    private static final double D_latitude = 40.44771;
    private static final double D_longitude = -79.93883;

    private int locationReportMode = CMU_MODE; // default is report CMU location

    private ArrayList<CreateSessionInvitationMessage> receivedInvitations;

    /** Sent Record - used for retransmission **/
    private ArrayList<InvitationSendRecord> sentInvitations;
    // key is the ChatSessionID
    // value is an array list of ChatSendRecord for that chat session
    private HashMap<String, ArrayList<ChatSendRecord>> sentChatMessages;

    private DatagramChannel UDPChannel = null;
    private SocketChannel socketChannel = null;

    private ArrayList<String> boundCompoments;
    private Thread t = null;
    volatile boolean stopthread = false;
    
    public void onCreate() {
        Log.e(TAG, "----- onCreate ---------");
        super.onCreate();

        num_servers = Integer.parseInt(this.getString(R.string.num_servers));

        populate_server_data();

        udp_port = Integer.parseInt(this.getString(R.string.udp_port));
        client = new NioClient(udp_port);

        int use_ssl = Integer.parseInt(this
                .getString(R.string.use_ssl_with_server));
        if (use_ssl == 1)
            use_ssl_with_server = true;

        lastKnownLocation = new Location(LOCATION_SERVICE);

        if (locationReportMode == CMU_MODE) {
            lastKnownLocation.setLongitude(cmu_longitude);
            lastKnownLocation.setLatitude(cmu_latitude);
        } else if (locationReportMode == WATERFRONT_MODE) {
            lastKnownLocation.setLongitude(waterfront_longitude);
            lastKnownLocation.setLatitude(waterfront_latitude);
        } else {
            // start with cmu in case GPS callback does not kick in
            lastKnownLocation.setLongitude(cmu_longitude);
            lastKnownLocation.setLatitude(cmu_latitude);
        }

        t = new Thread(client);
        t.start();
        Toolbox.setService(this);
    }

    public int onStartCommand(Intent intent, int flags, int startId) {
        // the service is starting due to a call to startService
        Log.d(TAG, "onStartCommand() is called");
        return 1;
    }

    /**
     * depending on 'num_servers' defined in strings.xml
     *
     * this method reads and stores the server information into arrays
     * 'server_ports' and 'server_ips'
     */
    private void populate_server_data() {
        server_ports = new int[num_servers];
        server_ips = new String[num_servers];

        if (num_servers == 1) {
            // server A
            server_ips[0] = this.getString(R.string.serverAip);
            server_ports[0] = Integer.parseInt(this
                    .getString(R.string.serverAport));
        } else if (num_servers == 2) {
            // servers A,B
            server_ips[0] = this.getString(R.string.serverAip);
            server_ports[0] = Integer.parseInt(this
                    .getString(R.string.serverAport));
            server_ips[1] = this.getString(R.string.serverBip);
            server_ports[1] = Integer.parseInt(this
                    .getString(R.string.serverBport));
        } else if (num_servers == 3) {
            // servers A,B,C
            server_ips[0] = this.getString(R.string.serverAip);
            server_ports[0] = Integer.parseInt(this
                    .getString(R.string.serverAport));
            server_ips[1] = this.getString(R.string.serverBip);
            server_ports[1] = Integer.parseInt(this
                    .getString(R.string.serverBport));
            server_ips[2] = this.getString(R.string.serverCip);
            server_ports[2] = Integer.parseInt(this
                    .getString(R.string.serverCport));
        } else if (num_servers == 4) {
            // servers A,B,C,D
            server_ips[0] = this.getString(R.string.serverAip);
            server_ports[0] = Integer.parseInt(this
                    .getString(R.string.serverAport));
            server_ips[1] = this.getString(R.string.serverBip);
            server_ports[1] = Integer.parseInt(this
                    .getString(R.string.serverBport));
            server_ips[2] = this.getString(R.string.serverCip);
            server_ports[2] = Integer.parseInt(this
                    .getString(R.string.serverCport));
            server_ips[3] = this.getString(R.string.serverDip);
            server_ports[3] = Integer.parseInt(this
                    .getString(R.string.serverDport));
        }
    }

    /**
     * This is the required method that allows this Service component to be
     * bound by other components, usually an Activity that needs any network
     * operations would bind to this Service component
     */
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "----- OnBind---------package=" + intent.getPackage()
                + "...component=" + intent.getComponent());
        initLocationListener();

        locMan = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        if (locationReportMode == REAL_MODE) {
            requestLocationUpdates();
        } else {
            stopLocationUpdates();
        }
        return mBinder;
    }

    private LocationListener gpsListener;
    private LocationManager locMan;

    private void initLocationListener() {
        gpsListener = new LocationListener() {

            boolean locationChanged = false;

            public void onLocationChanged(Location location) {
                // Log.d(TAG, "onLocationChanged() is called");

                if (locationReportMode == REAL_MODE) {
                    if (lastKnownLocation == null) {
                        lastKnownLocation = location;
                        locationChanged = true;
                    }
                } // else we simply ignore the detected location
                    // because we are simulating fake locations

                if (lastKnownLocation.getLatitude() == location.getLatitude()
                        && lastKnownLocation.getLongitude() == location
                                .getLongitude())
                    locationChanged = false;
                else
                    locationChanged = true;

                lastKnownLocation = location;
            }

            public void onProviderDisabled(String provider) {
                /*
                 * Toast.makeText(getApplicationContext(), "Gps Disabled",
                 * Toast.LENGTH_SHORT).show();
                 */
            }

            public void onProviderEnabled(String provider) {
                /*
                 * Toast.makeText(getApplicationContext(), "Gps Enabled",
                 * Toast.LENGTH_SHORT).show();
                 */
            }

            public void onStatusChanged(String provider, int status,
                    Bundle extras) {
            }
        };

    }

    private void requestLocationUpdates() {
        locMan.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
                gpsListener);
        locMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0,
                gpsListener);
    }

    private void stopLocationUpdates() {
        locMan.removeUpdates(gpsListener);
    }

    /**
     * prints out a debugging message so you know when some Activity unbinds
     * this Service component
     */
    public boolean onUnbind(Intent intent) {
        Log.e(TAG, "----- OnUnbind---------package=" + intent.getPackage()
                + "...component=" + intent.getComponent());
        return super.onUnbind(intent);
    }
    /**
     * prints out a debugging message so you know when some this Service
     * component is being destroyed
     */
    public void onDestroy() {
        Log.e(TAG, "----- onDestroy---------");

        if (UDPChannel != null) {
            DatagramSocket s = UDPChannel.socket();
            if (!s.isClosed()) {
                // System.out.println("the udp socket is not closed");
                s.close();
                // System.out.println("the udp socket is now closed");
            }
        }

        if (socketChannel != null) {
            Socket s = socketChannel.socket();
            if (!s.isClosed()) {
                // System.out.println("the tcp socket is not closed");
                try {
                    s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                // System.out.println("the tcp socket is now closed");
            }
        }
        Toolbox.setService(null);
        Log.e(TAG, "stopping thread t");
        stopthread = true;
    }

    private final IBinder mBinder = new LocalBinder();

    public class LocalBinder extends Binder {
        NetworkService getService() {
            // Return this instance of NetworkService
        	//  so clients can call public methods
            return NetworkService.this;
        }
    }

    /*************************** methods for clients ****************************/
    // clients here refer to those Activities that bind to this Service
    // component

    // isConnectedToServer
    public boolean isConnectedToServer() {
        return client.isConnectedToServer;
    }

    // login() - LogInActivity would call this method
    public void login(String username, String password) {
        Log.d(TAG, "login");

        // when register, use ClientLoginMessage
        ClientLoginMessage loginMessage = new ClientLoginMessage(username,
                password);

        lastTriedUsername = username;
        lastTriedPassword = password;

        Log.d(TAG, "C: Sending: '" + loginMessage + "'");
        try {
            client.sendTCP(loginMessage);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // logout() - DroidChatTabActivity would call this method
    public void logout() {
    	Log.d(TAG, "logout");
    	if(Toolbox.getMyInfo()!=null){
    		Log.d(TAG, "myInfo is not null yet");
    	}
    	
        if (login_record != null) {
            // when client logs out, use LogOutRequestFromClient
            LogOutRequestFromClient r = new LogOutRequestFromClient(
                    login_record.getLogin_session_id());
            try {
                System.out.println("sending out LogOutRequestFromClient");
                client.sendTCP(r);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        login_record = null;
        Toolbox.setMyInfo(null);
        Toolbox.removeAllChatSessions();
        Toolbox.removeAllUsers();
        lastTriedUsername = null;
        lastTriedPassword = null;
    }

    // register() - RegisterActivity would call this method
    public void register(UserInfo userInfo, String password) {
        Log.d(TAG, "register()");

        Drawable d = userInfo.getProfilePicture(); // the drawable (Captain Obvious, to the rescue!!!)

        Bitmap bitmap = Bitmap.createBitmap(
                d.getIntrinsicWidth(),
                d.getIntrinsicHeight(),
                d.getOpacity() == PixelFormat.OPAQUE ?
                    Bitmap.Config.RGB_565: Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            d.setBounds(0, 0, d.getIntrinsicWidth(),
                    d.getIntrinsicHeight());
            d.draw(canvas);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
        byte[] arr = baos.toByteArray();
        //Log.e(TAG, "byte array size=" + arr.length);
        //Log.e(TAG, "bg=" + userInfo.getMessageBackgroundColor().ordinal());

        // when register, use ClinetRegisterMessage
        ClientRegisterMessage registerMessage = new ClientRegisterMessage(
                userInfo.getUsername(), password, arr,
                userInfo.getMessageBackgroundColor().ordinal(),
                userInfo.getNickName()
                );

        Log.d(TAG, "C: Sending: '" + registerMessage + "'");
        try {
            client.sendTCP(registerMessage);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // setLocationReportMode() - DroidChatTabActivity would call this method
    public void setLocationReportMode(int mode) {
        Log.d(TAG, "setting location report mode to " + mode);
        locationReportMode = mode;
        if (locationReportMode == REAL_MODE) {
            requestLocationUpdates();
        } else {
            stopLocationUpdates();
            if (locationReportMode == CMU_MODE) {
                lastKnownLocation.setLongitude(cmu_longitude);
                lastKnownLocation.setLatitude(cmu_latitude);
            } else if (locationReportMode == WATERFRONT_MODE) {
                lastKnownLocation.setLongitude(waterfront_longitude);
                lastKnownLocation.setLatitude(waterfront_latitude);
            } else if (locationReportMode == LOCATION_A) {
                lastKnownLocation.setLongitude(A_longitude);
                lastKnownLocation.setLatitude(A_latitude);
            } else if (locationReportMode == LOCATION_B) {
                lastKnownLocation.setLongitude(B_longitude);
                lastKnownLocation.setLatitude(B_latitude);
            } else if (locationReportMode == LOCATION_C) {
                lastKnownLocation.setLongitude(C_longitude);
                lastKnownLocation.setLatitude(C_latitude);
            } else if (locationReportMode == LOCATION_D) {
                lastKnownLocation.setLongitude(D_longitude);
                lastKnownLocation.setLatitude(D_latitude);
            }
        }
    }

    // getUserPubKey() - ChatWindowActivity would call this method
    public void getUserPubKey(String username, String chatSessionID) {
        Log.e(TAG, "asking server for user's public key. " + username);

        if (login_record == null) {
            // facebook is hot and sexy
            return;
        }

        CreateSessionInfoRequestMessage askPubKey = new CreateSessionInfoRequestMessage(
                chatSessionID, login_record.getLogin_session_id(), username);
        System.out.println("sending CreateSessionInfoRequestMessage ");
        System.out.println("chatSessionID " + chatSessionID);

        try {
            client.sendTCP(askPubKey);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // sendInvitation() - DroidChatTabActivity would call this method
    public void sendInvitation(String invitee, String chatSessionID) {
        CreateSessionInvitationMessage invitation = new CreateSessionInvitationMessage(
                chatSessionID, Toolbox.getMyInfo().getUsername(), invitee,
                login_record.getPublicKey().getModulus(), login_record
                        .getPublicKey().getPublicExponent());

        System.out.println("sending CreateSessionInvitationMessage -> 1on1");
        System.out.println("chatSessionID " + chatSessionID);

        UserInfo receiver = Toolbox.retrieveUserInfoByUsername(invitee);

        if (receiver != null) {
            try {
                client.sendUDP(invitation, receiver.getUserIP());
                InvitationSendRecord r = new InvitationSendRecord(invitation,
                        System.currentTimeMillis(), receiver.getUserIP());
                synchronized (sentInvitations) {
                    sentInvitations.add(r);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // sendMultiUserInvitation() - ChatWindowActivity would call this method
    // when menu option - "Invite Neighbor to join" is clicked
    public void sendMultiUserInvitation(String invitee, String chatSessionID,
            HashMap<String, ParticipantLife> existingParticipantsInfo, 
            int curT) {

        Log.e(TAG + "sendMultiUserInvitation()", "chatSessionID="
                + chatSessionID);
        Log.e(TAG + "sendMultiUserInvitation()", "invitee=" + invitee);

        // key is username, value is 32-bit ip address
        HashMap<String, Integer> existingParticipants = new HashMap<String, Integer>();

        // fill in 'existingParticipants'
        for (String username : existingParticipantsInfo.keySet()) {
            if (existingParticipantsInfo.containsKey(username)) {
                ParticipantLife info = existingParticipantsInfo.get(username);
                existingParticipants.put(username,
                        (int) Toolbox.ipToInt(info.getIP()));
            }
        }

        // You don't include yourself in 'existingParticipants'
        CreateSessionInvitationMessage invitation = new CreateSessionInvitationMessage(
                chatSessionID, Toolbox.getMyInfo().getUsername(), invitee,
                login_record.getPublicKey().getModulus(), login_record
                        .getPublicKey().getPublicExponent());

        Log.e(TAG + "sendMultiUserInvitation()",
                "sending CreateSessionInvitationMessage -> multi-user");
        Log.e(TAG + "sendMultiUserInvitation()", "chatSessionID "
                + chatSessionID);

        invitation.setExistingParticipants(existingParticipants);
        invitation.setOneOnone(false);
        invitation.setT(curT);
        
        UserInfo receiver = Toolbox.retrieveUserInfoByUsername(invitee);

        boolean sent = true;

        if (receiver != null) {
            try {
                client.sendUDP(invitation, receiver.getUserIP());
            } catch (IOException e) {
                e.printStackTrace();
                sent = false;
            }
        }

        if (sent) {
            ChatSessionInfo chInfo = Toolbox.getChatSession(chatSessionID);
            chInfo.setPendingForMyInvitation(true);
            chInfo.setUsername(invitee);
            Log.e(TAG + "sendMultiUserInvitation()",
                    "ChatSession properties set -> multi-user");
            InvitationSendRecord r = new InvitationSendRecord(invitation,
                    System.currentTimeMillis(), receiver.getUserIP());
            synchronized (sentInvitations) {
                sentInvitations.add(r);
            }
        }
    }

    /*  Not calling this method anymore */
    public void respondToChatInvitationnew(String chatSessionID,
            String inviter, boolean answer, BigInteger mod, BigInteger exp) {
        UserInfo u = Toolbox.retrieveUserInfoByUsername(inviter);
        int ans;

        ChatSessionInfo chInfo = Toolbox.getChatSession(chatSessionID);

        if (answer) {
            // responding yes
            Log.e("respondToChatInvitation", "YES");
            ans = 1;
            chInfo.setPendingForMyResponse(false);
        } else {
            // responding no
            Log.e("respondToChatInvitation", "NO");
            ans = 0;
        }

        CreateSessionInvitationResponseMessage r = new CreateSessionInvitationResponseMessage(
                chatSessionID, ans, login_record.getUsername());

        EncryptCreateSessionInvitationResponseMessage enr = new EncryptCreateSessionInvitationResponseMessage(
                login_record.encryptothers(r, mod, exp));
        try {
            client.sendUDP(enr, u.getUserIP());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public void sendKeepAlive(String chatSessionID, boolean deKL){
    	ChatSessionInfo chInfo = Toolbox.getChatSession(chatSessionID);
    	
    	if(chInfo==null)
    		return;
    	
    	HashMap<String, ParticipantLife> chatParticipants = chInfo
        .getParticipants();

    	// figure out which participant is my neighbor
    	for (String username : chatParticipants.keySet()) {
    		UserInfo u = Toolbox.retrieveUserInfoByUsername(username);

    		// use the same round number to report to all neighbors
    		int KLRound = chInfo.getMyNextKLRound();

    		if (u.isANeighbor()) {
    			// send keep alive only participants that are my
    			// neighbors
    			String myIP = Toolbox.getLocalIpAddress();

    			if (myIP != null) {
    				ReportKeepAliveMessage kl = new ReportKeepAliveMessage(
    						chInfo.getSessionid(), Toolbox.getMyInfo()
    						.getUsername(),
    						(int) Toolbox.ipToInt(myIP), Toolbox
    						.getMyInfo().getUsername());
    				kl.setKLRound(KLRound);
    				kl.setDeKL(deKL);
    				try {
    					client.sendUDP(kl, u.getUserIP());
    					System.out
    					.println("--report keepalive, receiver is "
    							+ username
    							+ ", round is "
    							+ KLRound);
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    		}
    	}
    }

    // respondToChatInvitation() - the dialog in DroidChatTabActivity would call
    // this method
    public void respondToChatInvitation(String chatSessionID, String inviter,
            boolean answer) {
        UserInfo u = Toolbox.retrieveUserInfoByUsername(inviter);

        int ans;

        ChatSessionInfo chInfo = Toolbox.getChatSession(chatSessionID);

        if (answer) {
            // responding yes
            Log.e("respondToChatInvitation", "YES");
            ans = 1;
            chInfo.setPendingForMyResponse(false);
        } else {
            // responding no
            Log.e("respondToChatInvitation", "NO");
            ans = 0;
        }

        synchronized (receivedInvitations) {
            Iterator<CreateSessionInvitationMessage> itr = receivedInvitations
                    .iterator();
            while (itr.hasNext()) {
                CreateSessionInvitationMessage m = itr.next();

                if (m.getChatSessionID().equals(chatSessionID)
                        && m.getSenderUsername().equals(inviter)) {
                    itr.remove();

                    CreateSessionInvitationResponseMessage r = new CreateSessionInvitationResponseMessage(
                            chatSessionID, ans, login_record.getUsername());
                    try {
                        client.sendUDP(r, u.getUserIP());
                        InvitationSendRecord record = new InvitationSendRecord(
                                r, System.currentTimeMillis(), u.getUserIP());
                        synchronized (sentInvitations) {
                            sentInvitations.add(record);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    break;
                }
            }
        }
    }

    // disconnectFromServer() - whichever Activity that logs the user out would
    // call this method
    public void disConnectFromServer() {

    }

    public LoginSessionAttribute getLoginAttribute() {
        return login_record;
    }

    public void addBoundComponent(String ComponentName) {
        boundCompoments.add(ComponentName);
        Log.d("after addBoundComponent()", boundCompoments.toString());
    }

    public void removeBoundComponent(String ComponentName) {
        boundCompoments.remove(ComponentName);
        Log.d("after removeBoundComponent()", boundCompoments.toString());
    }

    public void sendChatMessage(ChatMessage msg) {
        Log.d(TAG, "sendChatMessage() is called");
        System.out.println("chat session id = " + msg.getChatSessionID());
        ChatSessionInfo chInfo = Toolbox.getChatSession(msg.getChatSessionID());

        /*System.out.println(chInfo);
        System.out.println(chInfo.getSessionid());
        System.out.println(msg.getMessageContent());
        System.out.println(msg.getUserinfo().getUsername());*/

        ChatSessionMessage message = new ChatSessionMessage(
                chInfo.getSessionid(), chInfo.getMyNextSeqNum(),
                msg.getMessageContent(), msg.getUserinfo().getUsername());

        message.setT(chInfo.incrementCurrrentT());
        msg.setT(message.getT());
        msg.setSeqNum(message.getSenderSeqNum());
        chInfo.addMyMessageDisplayed(msg);

        // key is the username
        // value is the ip
        HashMap<String, ParticipantLife> participants = chInfo
                .getParticipants();
        Iterator<String> itr = participants.keySet().iterator();

        while (itr.hasNext()) {
            String userName = itr.next();

            if (participants.containsKey(userName)) {
            	
            	try {
                    // the actual send operation
                    client.sendUDP(message, participants.get(userName).getIP());
                    
                    /*Log.e("username=", userName);
                    Log.e("ip=", participants.get(userName).getIP());*/
                    
                    // need to record the message being sent for retransmission
                    synchronized (sentChatMessages) {
                    	
                        if (!sentChatMessages.containsKey(msg
                                .getChatSessionID())) {
                            ArrayList<ChatSendRecord> record = new ArrayList<ChatSendRecord>();
                            ChatSendRecord newRecord = new ChatSendRecord(
                                    message, System.currentTimeMillis(),
                                    participants.get(userName).getIP(),
                                    userName);
                            newRecord.incrementSendCount(); // sendCount = 1
                            record.add(newRecord);
                            sentChatMessages
                                    .put(msg.getChatSessionID(), record);
                        } else {
                            ArrayList<ChatSendRecord> record = sentChatMessages
                                    .get(msg.getChatSessionID());
                            ChatSendRecord newRecord = new ChatSendRecord(
                                    message, System.currentTimeMillis(),
                                    participants.get(userName).getIP(),
                                    userName);
                            newRecord.incrementSendCount(); // sendCount = 1
                            record.add(newRecord);
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /********** end of methods for clients ***********/

    private class NioClient implements Runnable {

        private Selector selector;
        // private SocketChannel socketChannel;
        private SSLSocket serverSSLSocket;
        private SSLSession serverSSLSession;
        private LinkedList<ChangeRequest> pendingChanges;
        private LinkedList<ByteBuffer> pendingDataQueue;

        /*
         * pendingDataQueueUDP stores what needs to be sent out on the UDP port
         * [0] is the destination ip represented as a 4-byte int [1] is the
         * actually message
         */
        private LinkedList<ByteBuffer> pendingDataQueueUDP;

        /*
         * pendingIncomingData stores what the server sends to me through the
         * TCP pipe [0] is the byteSize header - should be 4 bytes, how many
         * bytes is the message [1] is the actually message - should be as long
         * as specified in 0th element
         */
        private LinkedList<ByteBuffer> pendingIncomingData;

        private boolean isConnectedToServer;

        private long reconnectRequestTime = 0;
        private boolean recconectResultReceived = false;
        private int reconnectCnt = 0;

        Random rand = new Random();

        /** randomized load balancing **/
        // generate a random number to decide which server to connect to
        int random_num = rand.nextInt(num_servers);
        int serverNum;

        private LinkedList<ChangeRequest> reregisterChangeReqs = new LinkedList<ChangeRequest>();

        public NioClient(int my_udp_port) {
            try {
                selector = initSelector();
                isConnectedToServer = false;

                serverNum = random_num % num_servers;
                Log.d(TAG, "serverNum=" + serverNum);

                socketChannel = connectToServer();

                pendingChanges = new LinkedList<ChangeRequest>();
                pendingDataQueue = new LinkedList<ByteBuffer>();
                pendingIncomingData = new LinkedList<ByteBuffer>();
                pendingDataQueueUDP = new LinkedList<ByteBuffer>();
                receivedInvitations = new ArrayList<CreateSessionInvitationMessage>();
                sentInvitations = new ArrayList<InvitationSendRecord>();
                sentChatMessages = new HashMap<String, ArrayList<ChatSendRecord>>();

                // not being used by Dennis right now
                boundCompoments = new ArrayList<String>();

                UDPChannel = DatagramChannel.open();
                DatagramSocket UDPSsocket = UDPChannel.socket();
                UDPChannel.configureBlocking(false);
                Log.d(TAG, "  calling bind on port " + my_udp_port);
                UDPSsocket.bind(new InetSocketAddress(my_udp_port));
                UDPChannel.register(selector, SelectionKey.OP_READ);

                /*
                 * Log.d(TAG, "  num_servers is " + num_servers); Log.d(TAG,
                 * "  servers A ip is " + server_ips[0]);
                 */

                // Queue a channel registration since the caller is not the
                // selecting thread. As part of the registration we'll register
                // an interest in connection events. These are raised when a
                // channel is ready to complete connection establishment.
                synchronized (pendingChanges) {
                    pendingChanges.add(new ChangeRequest(socketChannel,
                            ChangeRequest.REGISTER, SelectionKey.OP_CONNECT));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private SocketChannel connectToServer() throws IOException {

            if (DEBUG) {
                Log.d("connectToServer()", "" + serverNum);
            }

            String ipAddr = server_ips[serverNum];
            int portNum = server_ports[serverNum];

            if (DEBUG) {
                Log.d("connectToServer()", ipAddr);
                Log.d("connectToServer()", "" + portNum);
            }

            // Create a non-blocking socket channel
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);

            // Kick off connection establishment
            socketChannel.connect(new InetSocketAddress(ipAddr, portNum));
            return socketChannel;

        }

        public boolean isConnected() {
            return isConnectedToServer;
        }

        public void sendTCP(DroidChatMessage m) throws IOException {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(m);
            oos.flush();

            // queue the data we want written
            synchronized (pendingDataQueue) {
                ByteBuffer header = ByteBuffer.allocate(4);
                int byteSize = baos.toByteArray().length;
                header.putInt(byteSize);
                pendingDataQueue.add(ByteBuffer.wrap(header.array()));
                pendingDataQueue.add(ByteBuffer.wrap(baos.toByteArray()));
            }

            if (socketChannel.isConnected()) {
                synchronized (pendingChanges) {
                    this.pendingChanges.add(new ChangeRequest(socketChannel,
                            ChangeRequest.CHANGEOPS, SelectionKey.OP_WRITE));
                }
            }

            // Finally, wake up our selecting thread so it can make the required
            // changes
            this.selector.wakeup();
        }

        /*
         * insert data to be sent out on the UDP port wakes up the selecting
         * thread at the end so that selecting thread can perform the write
         * operation on the DatagramChannel
         */
        public void sendUDP(DroidChatMessage msg, String destination)
                throws IOException {

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(msg);
            oos.flush();

            ByteBuffer dest = ByteBuffer.allocate(4);
            dest.putInt((int) Toolbox.ipToInt(destination));

            // queue data to be written on UDP channel
            synchronized (pendingDataQueueUDP) {
                pendingDataQueueUDP.add(ByteBuffer.wrap(dest.array()));
                pendingDataQueueUDP.add(ByteBuffer.wrap(baos.toByteArray()));
            }
            Log.d(TAG, "inserted into pendingDataQueueUDP");
            synchronized (pendingChanges) {
                this.pendingChanges.add(new ChangeRequest(UDPChannel,
                        ChangeRequest.CHANGEOPS, SelectionKey.OP_WRITE));
            }
            this.selector.wakeup();
        }

        public void run() {
            try {
                while (true) {
                	if(stopthread==true){
                		Log.e(TAG, "thread stopped");
                		if (UDPChannel != null) {
                            DatagramSocket s = UDPChannel.socket();
                            if (!s.isClosed()) {
                                s.close();
                            }
                        }
                        if (socketChannel != null) {
                            Socket s = socketChannel.socket();
                            if (!s.isClosed()) {
                                try {
                                    s.close();
                                } catch (IOException e) {e.printStackTrace();}
                            }
                        }
                		break;
                	}
                	
                    synchronized (pendingChanges) {
                        for (ChangeRequest change : pendingChanges) {
                            switch (change.type) {
                            case ChangeRequest.CHANGEOPS:
                                SelectionKey key = change.socket
                                        .keyFor(this.selector);
                                
                                if(key==null){
                                	Log.e(TAG, "key is null");
                                	continue;
                                }
                                
                                try{
                                    key.interestOps(change.ops);
                                }catch(CancelledKeyException e){
                                    Log.e(TAG, "CancelledKeyException caught");
                                }
                                break;
                            case ChangeRequest.REGISTER:
                                change.socket.register(this.selector,
                                        change.ops);

                                if (DEBUG) {
                                    Log.d(TAG, "REGISTER CHANGE REQUEST ");
                                }
                                break;
                            case ChangeRequest.REREGISTER:
                                change.type = ChangeRequest.REGISTER;
                                reregisterChangeReqs.add(change);
                                break;
                            }
                        }
                        pendingChanges.clear();
                        pendingChanges.addAll(reregisterChangeReqs);
                        reregisterChangeReqs.clear();
                    }

                    // select() returns either when channel is ready for some IO
                    // operation
                    // or timeOut has passed
                    selector.select(ackTimeOut);

                    // check if it's time to do another location update with
                    // server
                    long timeDiff = System.currentTimeMillis() - lastUpdateTime;
                    long timeDiff2 = System.currentTimeMillis() - lastCheckTime;
                    
                    if (login_record != null && login_record.isLoggedIn()
                            && timeDiff >= timeOut && timeDiff2 >=timeOut) {

                    	lastCheckTime = System.currentTimeMillis();
                        int num = Toolbox.numNeighbors();
                        boolean needUpdate = Toolbox
                                .checkRoundNumber(updateRoundNumber);

                        // if (Toolbox.numNeighbors() != num) {
                        if (needUpdate) {
                            // tell NearbyUserListActivity to refresh the
                            // neighbor list content
                            broadcastUpdateNeighborList();

                            /**
                             * go through every chat session, what was pending may no
                             *  longer be pending anymore
                             */
                            ArrayList<ChatSessionInfo> existingSessions =
                                new ArrayList<ChatSessionInfo>(Toolbox.getAllChatSessions());

                            for (ChatSessionInfo ch: existingSessions) {
                                if(ch.isPendingForMyInvitation()){
                                    // check if the invitee is still a neighbor
                                    UserInfo invitee = Toolbox.retrieveUserInfoByUsername(ch.getUsername());
                                    if(invitee.isANeighbor()==false){
                                        Log.e("update in neigbhor list " + ch.getSessionid(),
                                                "ivitee " + invitee.getUsername() +
                                                " is no longer a neigbor");
                                        if(ch.numberOfParticipants()==0){
                                            Log.e("update in neigbhor list " + ch.getSessionid(),
                                                    "no other participant left");
                                            // probably want to remove chat session and broadcast to
                                            //  ChatSessionListActivity/ChatWindowActivity
                                            //Toolbox.removeChatSession(ch.getSessionid());
                                            ch.setUsername(null);
                                            ch.setPendingForMyInvitation(false);
                                            ch.setDead(true);
                                            Toolbox.removeChatSession(ch.getSessionid(), false);
                                            broadcastChangeInChatSession(ch);
                                        } else {
                                        	Log.e("update in neigbhor list " + ch.getSessionid(),
                                            	"still some participant left");
                                            // don't remove chat session
                                            // set chat session to non-pending
                                            ch.setUsername(null);
                                            ch.setPendingForMyInvitation(false);
                                            broadcastChangeInChatSession(ch);
                                        }
                                    }
                                }else if(ch.isPendingForMyResponse()){
                                    // check if the inviter is still a neighbor
                                    UserInfo inviter = Toolbox.retrieveUserInfoByUsername(ch.getInviter());
                                    if(inviter.isANeighbor()==false){
                                        Log.e("update in neigbhor list " + ch.getSessionid(),
                                                "inviter " + inviter.getUsername() +
                                                " is no longer a neigbor");
                                        if(ch.numberOfParticipants()==0){
                                            Log.e("update in neigbhor list " + ch.getSessionid(),
                                                    "no other participants ==> 1-on-1 chat session");
                                            // probably want to remove chat session and broadcast to
                                            //  ChatSessionListActivity/ChatWindowActivity
                                            //Toolbox.removeChatSession(ch.getSessionid());
                                            Toolbox.removeChatSession(ch.getSessionid(), true);
                                            broadcastChangeInChatSession(ch);
                                        }
                                    }
                                }else{
                                	
                                }
                            }

                        }
                        // it's time do another round of location update with
                        // server
                        Log.e("isConnectedToServer", ""+isConnectedToServer);
                        if (isConnectedToServer == true) {
                        	if(tempDoNotUpdate==false){
                        		sendLocationUpdateMessage();
                        	}
                        } else {
                        	// i should not send anything to server but shall
                        	//  still increment updateRoundNumber
                        	++updateRoundNumber;
                        }
                    }

                    if (isConnectedToServer == false) {
                        timeDiff = System.currentTimeMillis()
                                - reconnectRequestTime;

                        if (timeDiff > 500 && recconectResultReceived==true) {
                            Log.e("RECONNECT", "RECONNECTING");
                            recconectResultReceived = false;
                            random_num = rand.nextInt(num_servers);
                            if( (random_num%num_servers)==serverNum ) {
                            	random_num = rand.nextInt(num_servers);
                            }
                            serverNum = random_num % num_servers;

                            if(socketChannel!=null && socketChannel.isOpen()){
                                Log.e(TAG, "before reconnection: closing socket channel");
                                socketChannel.close();
                            }

                            socketChannel = connectToServer();

                            // have to let select() know you're interested
                            // in OP_CONNECT
                            synchronized (pendingChanges) {
                                pendingChanges.add(new ChangeRequest(
                                        socketChannel, ChangeRequest.REGISTER,
                                        SelectionKey.OP_CONNECT));
                            }
                            reconnectRequestTime = System.currentTimeMillis();
                            
                            int numNeighbors = Toolbox.numNeighbors();
                            int numChats = Toolbox.numChatSessions();
                            
                            Log.e("numNeighbors", ""+numNeighbors);
                            Log.e("numChats", ""+numChats);
                            
                            if(numNeighbors==0 && numChats==0){
                            	reconnectCnt++;
                            }else{
                            	reconnectCnt=0;
                            }
                            Log.e("reconnectCnt", "" + reconnectCnt);
                            
                            if(reconnectCnt==5){
                            	lastTriedUsername=null;
                            	lastTriedPassword=null;
                            	broadcastLogoutCommand();
                            	reconnectCnt = 0;
                            }
                        }
                    }

                    // go through the channels that select() found to be ready
                    // for some IO operation
                    Set<SelectionKey> selectKeys = selector.selectedKeys();
                    Iterator<SelectionKey> keys = selectKeys.iterator();

                    while (keys.hasNext()) {
                        SelectionKey key = keys.next();

                        if(key.isValid()==false){
                        	keys.remove();
                        	continue;
                        }
                        
                        if (key.isConnectable()) {

                            if (DEBUG) {
                                Log.d(TAG, " connectable ");
                            }

                            boolean result = false;

                            if (use_ssl_with_server) {
                                result = finishConnectionSSL(key,
                                        server_ips[serverNum],
                                        server_ports[serverNum]);
                            } else {
                                result = finishConnection(key);
                            }

                            if (DEBUG) {
                                Log.d(TAG, " connect result is " + result);
                            }

                            recconectResultReceived = true;
                            if (result == true) {                  	
                            	Log.e("TCP_NO_DELAY", ""+socketChannel.socket().getTcpNoDelay());
                            	socketChannel.socket().setTcpNoDelay(true);
                            	Log.e("TCP_NO_DELAY", ""+socketChannel.socket().getTcpNoDelay());
                                isConnectedToServer = true;

                                if(login_record!=null && lastTriedUsername!=null && lastTriedPassword!=null){
                                	tempDoNotUpdate = true;
                                	login(lastTriedUsername, lastTriedPassword);
                                	tempIgnoreLogOut = true;
                                }
                                
                                // Register an interest in writing on this
                                // channel, only if there is data to be written
                                synchronized (pendingDataQueue) {
                                    if (!pendingDataQueue.isEmpty()) {
                                        Log.d(TAG,
                                                " pendingDataQueue is not empty ");
                                        key.interestOps(SelectionKey.OP_WRITE);
                                    } else {
                                        if (use_ssl_with_server) {
                                            // the thing with SSL is that client
                                            // shall always
                                            // send something first to force the
                                            // SSL handshake
                                            SSLClientDummyMessage m = new SSLClientDummyMessage();
                                            client.sendTCP(m);
                                        }
                                        key.interestOps(SelectionKey.OP_READ);
                                    }
                                }
                                
                                reportKeepAlive2();
                            } else {
                                // connection failed, try next server
                                isConnectedToServer = false;
                                reconnectRequestTime = System
                                        .currentTimeMillis();
                            }

                        } else if (key.isWritable()) {
                            // Log.d(TAG, " writable ");
                            if (key.channel() == UDPChannel) {
                                Log.d(TAG, "UDP Writable");
                                // try{
                                writeUDP(key);
                            } else if (key.channel() == socketChannel) {
                                // Log.d(TAG, "TCP Writeable");
                                if (use_ssl_with_server) {
                                    writeTCPSSL(key);
                                } else {
                                    writeTCP(key);
                                }
                            }
                        } else if (key.isReadable()) {
                            // Log.d(TAG, " readable ");
                            if (key.channel() == UDPChannel) {
                                //Log.d(TAG, " UDP readable ");
                                readUDP(key);
                            } else if (key.channel() == socketChannel) {
                                // Log.d(TAG, " TCP readable ");
                                try {
                                    int error = 0;

                                    if (use_ssl_with_server) {
                                        error = readTCPSSL(key);
                                    } else {
                                        error = readTCP(key);
                                    }
                                    if (error == -1) {
                                        Log.e(TAG,
                                                "Connection dropped. Probably server died");

                                        isConnectedToServer = false;
                                        reconnectRequestTime = System
                                                .currentTimeMillis();
                                    }
                                } catch (SocketException e) {
                                    Log.e(TAG,
                                            "SocketException caught while doing readTCP()");

                                    key.channel().close();
                                    key.cancel();

                                    isConnectedToServer = false;
                                    reconnectRequestTime = System
                                            .currentTimeMillis();
                                }
                            }

                        }
                        // the channel that's ready for IO operation has been
                        // taken care of
                        keys.remove();
                    }

                    // check for the need to retransmit
                    checkRetransmission();

                    // periodically I've got to tell all the participants of my
                    // chat session
                    // which participants are my neighbors
                    long ddd = System.currentTimeMillis() - lastReportNeigborTime;
                    if (ddd > 5000 && login_record != null) {
                    	System.out.println("ddd=" + ddd);
                        reportKeepAlive();
                        lastReportNeigborTime = System.currentTimeMillis();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        private void reportKeepAlive2() {
        	System.out.println("reportKeepAlive2()");

            // how many chat sessions am I currently in?
            ArrayList<ChatSessionInfo> chats = new ArrayList<ChatSessionInfo>(
                    Toolbox.getAllChatSessions());
            
            // for each chat session that I am currently in
            for (ChatSessionInfo chInfo : chats) {
            	if(chInfo.isDead())
            		continue;

                HashMap<String, ParticipantLife> chatParticipants = chInfo
                        .getParticipants();

                int oldNum = chInfo.numberOfParticipants();

                // figure out which participant is my neighbor
                for (String username : chatParticipants.keySet()) {
                    UserInfo u = Toolbox.retrieveUserInfoByUsername(username);

                    // use the same round number to report to all neighbors
                    int KLRound = chInfo.getMyNextKLRound();

                    if (u.isANeighbor()) {
                        // send keep alive only participants that are my
                        // neighbors
                        String myIP = Toolbox.getLocalIpAddress();
                        if (myIP != null) {
                            ReportKeepAliveMessage kl = new ReportKeepAliveMessage(
                                    chInfo.getSessionid(), Toolbox.getMyInfo()
                                            .getUsername(),
                                    (int) Toolbox.ipToInt(myIP), Toolbox
                                            .getMyInfo().getUsername());
                            kl.setKLRound(KLRound);
                            try {
                                client.sendUDP(kl, u.getUserIP());
                                System.out
                                        .println("report keepalive, receiver is "
                                                + username
                                                + ", round is "
                                                + KLRound);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }

        private void reportKeepAlive() {
            System.out.println("reportKeepAlive()");

            // how many chat sessions am I currently in?
            ArrayList<ChatSessionInfo> chats = new ArrayList<ChatSessionInfo>(
                    Toolbox.getAllChatSessions());

            //System.out.println("number of chat sessions=" + chats.size());

            // for each chat session that I am currently in
            for (ChatSessionInfo chInfo : chats) {

            	if(chInfo.isDead())
            		continue;

            	String oldname = chInfo.getSessionid();
            	
                HashMap<String, ParticipantLife> chatParticipants = chInfo
                        .getParticipants();

                int oldNum = chInfo.numberOfParticipants();

                // figure out which participant is my neighbor
                for (String username : chatParticipants.keySet()) {
                    UserInfo u = Toolbox.retrieveUserInfoByUsername(username);

                    // use the same round number to report to all neighbors
                    int KLRound = chInfo.getMyNextKLRound();

                    if (u.isANeighbor()) {
                        // send keep alive only participants that are my
                        // neighbors
                        String myIP = Toolbox.getLocalIpAddress();

                        if (myIP != null) {
                            ReportKeepAliveMessage kl = new ReportKeepAliveMessage(
                                    chInfo.getSessionid(), Toolbox.getMyInfo()
                                            .getUsername(),
                                    (int) Toolbox.ipToInt(myIP), Toolbox
                                            .getMyInfo().getUsername());
                            kl.setKLRound(KLRound);
                            try {
                                client.sendUDP(kl, u.getUserIP());
                                System.out
                                        .println("report keepalive, receiver is "
                                                + username
                                                + ", round is "
                                                + KLRound);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                    // also take this chance to call decreasettl()
                    Log.e(TAG, "calling decreasettl() on " + username);
                    chInfo.decreasettl(username);
                    
                    if(chInfo.isAParticipant(username)==false){
                    	Log.e("after decreasettl()",
                        "chat session participant gone=" + username);
                    	
                    	System.out.println("old name=" + oldname);
                    	System.out.println("new name=" + chInfo.getSessionid());
                    	
                    	if(chInfo.isNotResponsiveParticipant(username)==false){
                    		Log.e("after decreasettl()", "isNotResponsiveParticipant is false");
                    		synchronized(sentChatMessages){
                    			if(sentChatMessages.containsKey(oldname)){
                    				ArrayList<ChatSendRecord> list = sentChatMessages.get(oldname);
                    				Iterator<ChatSendRecord> itr = list.iterator();
                    				while(itr.hasNext()){
                    					ChatSendRecord r = itr.next();
                    					if(r.getRecipientUsername().equals(username)){
                    						r.setTimeSent(100);
                    						chInfo.setParticipantNonResponsive(username);
                                			// make ChatWindowActivity display that
                                    		//  "XX is not responsive" right after record.getMessage()
                                    		chInfo.addSystemMessageNotResponsive(r.getMessage(), r.getRecipientUsername());
                                    		broadcastMessageReceived(r.getMessage());
                                    		break;
                    					}
                    				}
                    			}
                    		}
                    	}
                    	
                    	broadcastChangeInChatSession(chInfo);
                    }
                }

                // after calling decreasettl(),

                // 1) the chat session may be gone
                // due to zero participants left in chInfo
                ChatSessionInfo ch = Toolbox.getChatSession(chInfo
                        .getSessionid());
                if (ch == null) {
                    // the chat session is gone
                    // broadcast to ChatWindowActivity/ChatSessionListActivity
                    Log.e("after decreasettl()",
                            "chat session gone: " + chInfo.getSessionid());
                    broadcastChangeInChatSession(chInfo);
                } else {
                    // 2) the chat session participants may change
                    // some participant getting killed
                    int newNum = ch.numberOfParticipants();
                    if (newNum != oldNum) {
                        // the number of chat session participant has decreased
                        // broadcast to
                        // ChatWindowActivity/ChatSessionListActivity
                        /*Log.e("after decreasettl()",
                                "chat session participant gone");
                        
                        broadcastChangeInChatSession(ch);*/
                    }
                }
            }
        }

        private void reportMyNeighbors() {
            System.out.println("reportMyNeighbors()");

            // how many chat sessions am I currently in?
            ArrayList<ChatSessionInfo> chats = new ArrayList<ChatSessionInfo>(
                    Toolbox.getAllChatSessions());

            System.out.println("number of chat sessions=" + chats.size());

            // for each chat session that I am currently in
            for (ChatSessionInfo chInfo : chats) {

                // my neighbors that are in this chat session
                ArrayList<NeighborInfo> lists = new ArrayList<NeighborInfo>();
                HashMap<String, ParticipantLife> chatParticipants = chInfo
                        .getParticipants();
                System.out.println("session id is " + chInfo.getSessionid());

                // this for loop is used to construct a list of my neighbors
                // that are
                // in the chat session
                for (String username : chatParticipants.keySet()) {

                    chInfo.decreasettl(username);

                    UserInfo u = Toolbox.retrieveUserInfoByUsername(username);
                    if (u.isANeighbor()) {
                        int ip = (int) Toolbox.ipToInt(u.getUserIP());
                        lists.add(new NeighborInfo(chInfo.getSessionid(), u
                                .getUsername(), ip));
                    }
                    int oldNum = chInfo.numberOfParticipants();

                    // after calling decreasettl(),

                    // 1) the chat session may be gone
                    // due to zero participants left in chInfo
                    ChatSessionInfo ch = Toolbox.getChatSession(chInfo
                            .getSessionid());
                    if (ch == null) {
                        // the chat session is gone
                        // broadcast to
                        // ChatWindowActivity/ChatSessionListActivity
                        Log.e("after decreasettl()", "chat session gone: "
                                + chInfo.getSessionid());
                        broadcastChangeInChatSession(chInfo);
                    } else {
                        // 2) the chat session participants may change
                        // some participant getting killed
                        int newNum = ch.numberOfParticipants();
                        if (newNum != oldNum) {
                            // the number of chat session participant has
                            // decreased
                            // broadcast to
                            // ChatWindowActivity/ChatSessionListActivity
                            Log.e("after decreasettl()",
                                    "chat session participant gone");
                            broadcastChangeInChatSession(ch);
                        }
                    }
                }

                // includes my username as the 'reporter'
                ReportNeighborMessage nbrmsg = new ReportNeighborMessage(
                        chInfo.getSessionid(), lists,
                        login_record.getUsername());

                // once the list is completed, my job is to send this list to
                // every participant
                for (String username : chatParticipants.keySet()) {
                    String receiverIP = chatParticipants.get(username).getIP();
                    try {
                        client.sendUDP(nbrmsg, receiverIP);
                        System.out.println("report neighbor, receiver is "
                                + username);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        // the selecting thread calls this method to go over the
        // list of transmitted UDP messages that have not yet been
        // ACKed
        private void checkRetransmission() {

            long curTime = System.currentTimeMillis();

            /*** retransmission of invitation or invitation response ***/

            synchronized (sentInvitations) {
                Iterator<InvitationSendRecord> itr = sentInvitations.iterator();

                while (itr.hasNext()) {
                    InvitationSendRecord m = itr.next();

                    String chatSessionID = m.getMessage().getChatSessionID();

                    if ((curTime - m.getTimeSent()) > ackTimeOut) {
                        m.setTimeSent(System.currentTimeMillis());
                        try {
                            client.sendUDP(m.getMessage(), m.getDestinationIP());

                            if (m.getMessage() instanceof CreateSessionInvitationMessage) {
                                System.out.println("   invitation"
                                        + m.getMessage().getChatSessionID()
                                        + " needs retransmission");
                            } else if (m.getMessage() instanceof CreateSessionInvitationResponseMessage) {
                                System.out.println("   invitation response"
                                        + m.getMessage().getChatSessionID()
                                        + " needs retransmission");
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

            synchronized (sentChatMessages) {
                Iterator<String> chatSessionIDs = sentChatMessages.keySet()
                        .iterator();

                while (chatSessionIDs.hasNext()) {
                    String chatSessionID = chatSessionIDs.next();

                    // double check if the chat session still exists
                    ChatSessionInfo chInfo = Toolbox
                            .getChatSession(chatSessionID);

                    if (chInfo == null) {
                        // chat session is gone
                        System.out.println("chat session: " + chatSessionID
                                + " is gone."
                                + " removing from sentChatMessages");
                        chatSessionIDs.remove();
                        continue;
                    }

                    ArrayList<ChatSendRecord> list = sentChatMessages
                            .get(chatSessionID);

                    Iterator<ChatSendRecord> iterator = list.iterator();

                    while (iterator.hasNext()) {
                        ChatSendRecord record = iterator.next();

                        if(chInfo.hasParticipant(record.getRecipientUsername())){
                            if ( record.getSendCount() < 5 ) {
	                        	if (curTime-record.getTimeSent() > ackTimeOut) {
	                                Log.e(TAG, "   chat session message "
	                                        + record.getMessage()
	                                                .getChatSessionID() + " "
	                                        + record.getMessage().getSenderSeqNum()
	                                        + " to " + record.getRecipientUsername()
	                                        + " needs retransmission");
	                                record.setTimeSent(System.currentTimeMillis());
	                                record.incrementSendCount();
	                                try {
	                                    client.sendUDP(record.getMessage(),
	                                            record.getDestinationIP());
	                                    Log.e(TAG, "retransmitting to " 
	                                    		+ record.getDestinationIP());
	                                } catch (IOException e) {
	                                    e.printStackTrace();
	                                }
	                            }
                            } else {
                            	// check that this guy is in the NOT_RESPONSIVE_LIST                            	
                            	if(chInfo.isNotResponsiveParticipant(record.getRecipientUsername())
                            			==false){
                                	//  if not, add him into the NOT_RESPONSIVE_LIST
                            		chInfo.setParticipantNonResponsive(record.getRecipientUsername());
                            		Log.e("chat session "+chInfo.getSessionid(), record.getRecipientUsername()
                            				+ " has been detected as NOT RESPONSIVE");
                            		
                            		// make ChatWindowActivity display that
                            		//  "XX is not responsive" right after record.getMessage()
                            		chInfo.addSystemMessageNotResponsive(record.getMessage(), record.getRecipientUsername());
                            		broadcastMessageReceived(record.getMessage());
                            	}
                            }
                        } else {
                            iterator.remove();
                            System.out
                                    .println("   chat session message "
                                            + record.getMessage()
                                                    .getChatSessionID()
                                            + " "
                                            + record.getMessage()
                                                    .getSenderSeqNum()
                                            + " no longer needs retransmission bc participant gone");
                        }
                    }
                }
            }
        }

        // the selecting thread calls this method to perform IO on the channel
        public void writeTCP(SelectionKey key) throws IOException {
            SocketChannel socketChannel = (SocketChannel) key.channel();

            boolean error = false;

            Log.d(TAG, "writeTCP()" + ((SocketChannel)key.channel()).socket().getTcpNoDelay());
            
            synchronized (pendingDataQueue) {
                while (!pendingDataQueue.isEmpty()) {
                    ByteBuffer buf = (ByteBuffer) pendingDataQueue.get(0);
                    int bytesWritten = 0;

                    try {
                        bytesWritten = socketChannel.write(buf);
                    } catch (SocketException e) {
                        error = true;
                        isConnectedToServer = false;
                        reconnectRequestTime = System.currentTimeMillis();
                    }

                    // Log.d(TAG, bytesWritten + " bytes written");

                    if (error == false) {
                        if (buf.remaining() > 0) {
                            // ... or the socket's buffer fills up
                            Log.e(TAG, "the socket's buffer fills up");
                            break;
                        }
                    }
                    pendingDataQueue.remove(0);
                }

                if (pendingDataQueue.isEmpty()) {
                    // We wrote away all data, so we're no longer interested
                    // in writing on this socket. Switch back to waiting for
                    // data.
                    key.interestOps(SelectionKey.OP_READ);
                }

            }
        }

        public void writeUDP(SelectionKey key) throws IOException {
            DatagramChannel datagramChannel = (DatagramChannel) key.channel();

            boolean error = false;

            synchronized (pendingDataQueueUDP) {
                while (!pendingDataQueueUDP.isEmpty()) {
                    ByteBuffer dest = pendingDataQueueUDP.get(0);
                    ByteBuffer data = pendingDataQueueUDP.get(1);

                    String destination = Toolbox.intToIp(dest.getInt());
                    InetSocketAddress target = new InetSocketAddress(
                            destination, udp_port);

                    try {
                        datagramChannel.send(data, target);
                    } catch (SocketException e) {
                        Log.e(TAG,
                                "SocketException caught while doing udp send()");
                        error = true;
                    }

                    if (error == false) {
                        if (data.remaining() > 0) {
                            // ... or the socket's buffer fills up
                            Log.e(TAG, "the socket's buffer fills up");
                            break;
                        }
                    }

                    pendingDataQueueUDP.remove();
                    pendingDataQueueUDP.remove();
                }

                if (pendingDataQueueUDP.isEmpty()) {
                    key.interestOps(SelectionKey.OP_READ);
                }
            }
        }

        public void readUDP(SelectionKey key) throws IOException {
            // Log.d(TAG, "readUDP()");
            DatagramChannel ch = (DatagramChannel) key.channel();
            ByteBuffer temp = ByteBuffer.allocate(65535);

            SocketAddress sender = null;
            try {
                sender = ch.receive(temp);
            } catch (IOException e1) {
                e1.printStackTrace();
            }

            if (sender == null) {
                Log.d(TAG, "sender is null");
            } else {
                // Log.d(TAG, "sender is " + sender.toString());
                // Log.d(TAG, "after receive position=" + temp.position());

                byte[] inputBuffer = new byte[temp.position()];
                temp.flip();
                temp.get(inputBuffer);

                ByteArrayInputStream bais = new ByteArrayInputStream(
                        inputBuffer);
                ObjectInputStream ois = null;
                ois = new ObjectInputStream(bais);

                DroidChatMessage msg = null;
                try {
                    msg = (DroidChatMessage) ois.readObject();
                } catch (ClassNotFoundException e) {

                } catch (EOFException ee){
                    Log.e(TAG, "end of file exception");
                }

                try {
                    handleResponse(msg);
                } catch (NoSuchPaddingException e) {
                    e.printStackTrace();
                }
            }
        }

        public int readTCP(SelectionKey key) throws IOException {
            if (DEBUG) {
                Log.d(TAG, " read() called ");
            }
            SocketChannel socketChannel = (SocketChannel) key.channel();

            int numRead;

            if (pendingIncomingData.isEmpty()) {
                pendingIncomingData.add(ByteBuffer.allocate(4)); // [0] is the
                // byteSize
                // of the
                // message

                numRead = socketChannel.read(pendingIncomingData.get(0));
                if (numRead == -1) {
                    // Remote entity shut the socket down cleanly. Do the
                    // same from our end and cancel the channel.
                    key.channel().close();
                    key.cancel();
                    isConnectedToServer = false;
                    return (-1);
                } else if (numRead == 4) {
                    pendingIncomingData.get(0).flip();
                    int byteSize = pendingIncomingData.get(0).getInt();
                    Log.d(TAG, numRead
                            + " bytes read from server. Message byteSize:"
                            + byteSize);

                    pendingIncomingData.add(ByteBuffer.allocate(byteSize)); // [1]
                    // is
                    // the
                    // actual
                    // message
                    ByteBuffer actualMsg = pendingIncomingData.get(1);

                    numRead = socketChannel.read(actualMsg);

                    if (numRead == -1) {
                        // Remote entity shut the socket down cleanly. Do the
                        // same from our end and cancel the channel.
                        key.channel().close();
                        key.cancel();
                        isConnectedToServer = false;
                        return (-1);
                    } else {
                        Log.d(TAG, numRead + " bytes read from server");
                        if (actualMsg.remaining() == 0) {
                            // turn bytes into DroidChatMessage
                            ByteArrayInputStream bais = new ByteArrayInputStream(
                                    actualMsg.array());
                            ObjectInputStream ois = new ObjectInputStream(bais);
                            DroidChatMessage m = null;
                            try {
                                m = (DroidChatMessage) ois.readObject();
                            } catch (ClassNotFoundException e) {
                                e.printStackTrace();
                            }
                            // Handle the response
                            try {
                                handleResponse(m);
                            } catch (NoSuchPaddingException e) {
                                e.printStackTrace();
                            }
                            pendingIncomingData.clear();
                        }
                    }
                }
            } else {
                ByteBuffer byteSizeBuf = pendingIncomingData.get(0);
                int remaining = byteSizeBuf.remaining();

                if (remaining == 0) {
                    // I already know how many bytes the message is
                    ByteBuffer actualMsg = pendingIncomingData.get(1);
                    numRead = socketChannel.read(actualMsg);

                    if (numRead == -1) {
                        // Remote entity shut the socket down cleanly. Do the
                        // same from our end and cancel the channel.
                        key.channel().close();
                        key.cancel();
                        isConnectedToServer = false;
                        return (-1);
                    } else {
                        Log.d(TAG, numRead + " bytes read from server");
                        if (actualMsg.remaining() == 0) {
                            // turn bytes into DroidChatMessage
                            ByteArrayInputStream bais = new ByteArrayInputStream(
                                    actualMsg.array());
                            ObjectInputStream ois = new ObjectInputStream(bais);
                            DroidChatMessage m = null;
                            try {
                                m = (DroidChatMessage) ois.readObject();
                            } catch (ClassNotFoundException e) {
                                e.printStackTrace();
                            }
                            // Handle the response
                            try {
                                handleResponse(m);
                            } catch (NoSuchPaddingException e) {
                                e.printStackTrace();
                            }
                            pendingIncomingData.clear();
                        }
                    }
                } else {
                    numRead = socketChannel.read(byteSizeBuf);
                    if (numRead == -1) {
                        // Remote entity shut the socket down cleanly. Do the
                        // same from our end and cancel the channel.
                        key.channel().close();
                        key.cancel();
                        System.out.println("numRead=-1");
                        pendingIncomingData.remove(socketChannel);
                        return (-1);
                    } else if (numRead == remaining) {
                        byteSizeBuf.flip();
                        int byteSize = byteSizeBuf.getInt();

                        // finished reading in the byteSize
                        Log.d(TAG, "Message byteSize:" + byteSize);

                        pendingIncomingData.add(ByteBuffer.allocate(byteSize)); // [1]
                        // is
                        // the
                        // actual
                        // message
                        ByteBuffer actualMsg = pendingIncomingData.get(1);

                        numRead = socketChannel.read(actualMsg);
                        if (numRead == -1) {
                            // Remote entity shut the socket down cleanly. Do
                            // the
                            // same from our end and cancel the channel.
                            key.channel().close();
                            key.cancel();
                            System.out.println("numRead=-1");
                            pendingIncomingData.remove(socketChannel);
                            return (-1);
                        } else {
                            if (actualMsg.remaining() == 0) {
                                // turn bytes into DroidChatMessage
                                ByteArrayInputStream bais = new ByteArrayInputStream(
                                        actualMsg.array());
                                ObjectInputStream ois = new ObjectInputStream(
                                        bais);
                                DroidChatMessage m = null;
                                try {
                                    m = (DroidChatMessage) ois.readObject();
                                } catch (ClassNotFoundException e) {
                                    e.printStackTrace();
                                }
                                // Handle the response
                                try {
                                    handleResponse(m);
                                } catch (NoSuchPaddingException e) {
                                    e.printStackTrace();
                                }
                                pendingIncomingData.clear();
                            }
                        }
                    }
                }
            }
            return 0;
        }

        // upon receiving a DroidChatMessage
        private void handleResponse(DroidChatMessage m)
                throws NoSuchPaddingException, IOException {

            if (m instanceof ClientRegisterResponseMessage) {
                ClientRegisterResponseMessage mm = (ClientRegisterResponseMessage) m;
                Log.d(TAG, " ClientRegisterResponseMessage=" + mm);
                Log.d(TAG, " in handleResponse(): " + mm.getUsername());
                broadcastRegisterResponse(mm);
            } else if (m instanceof ClientLoginResponseMessage) {
                ClientLoginResponseMessage mm = (ClientLoginResponseMessage) m;
                Log.d(TAG, " ClientLoginResponseMessage=" + mm);
                Log.d(TAG, " in handleResponse(): " + mm.getUsername());
                tempIgnoreLogOut = false;
                
                if(lastTriedUsername!=null && lastTriedUsername.equals(mm.getUsername())){
                    if(mm.getResponse()==ClientLoginResponseMessage.LOGIN_SUCCESSFUL){
                    	Log.e("ClientLoginResponseMessage", "calling removeAllUsers()");
                    	Toolbox.removeAllUsers();
                    	
	                    login_record = new LoginSessionAttribute(
	                        mm.getLoginSessionID(), mm.getUsername());
	                    if (tempDoNotUpdate==true) {
	                    	tempDoNotUpdate = false;
	                    }
	                    try {
	                        client.sendTCP(new ClientPublicKeyMessage(mm
	                                .getLoginSessionID(), login_record.getPublicKey()));
	                    } catch (IOException e) {
	                        e.printStackTrace();
	                    }
                    }
                    broadcastLoginResponse(mm);
                }
            } else if (m instanceof NeighborInfoMessage) {
                NeighborInfoMessage mm = (NeighborInfoMessage) m;
                /*
                 * Log.d(TAG, " NeighborInfoMessage=" + mm); Log.d(TAG,
                 * " in handleResponse(): " + mm.getUsername()); Log.d(TAG,
                 * " in response to round: " + mm.getRound());
                 */

                if (mm.getRound() == updateRoundNumber) {
                    // Log.d(TAG, " matches with updateRoundNumber:" +
                    // updateRoundNumber);

                    broadcastNeighborInfo(mm);
                }

            } else if (m instanceof LogOutCommandFromServer) {
                // server telling me to log out
            	if(tempIgnoreLogOut==false){
            		broadcastLogoutCommand();
            		Log.e("LogOutCommandFromServer", "got logged out from server");
            	} else {
            		Log.e("LogOutCommandFromServer", "got logged out from server but (tempIgnoreLogOut) is true");
            	}
            	tempIgnoreLogOut = false;
                // don't do these here, let the activity handle it

                /*
                 * login_record = null; Toolbox.setMyInfo(null);
                 */
                /*UserInfo me = Toolbox.getMyInfo();

                if(me!=null){
                    // I am currently logged in
                    // really should send another login command for the user
                    login(lastTriedUsername, lastTriedPassword);
                    Log.e("LastLoginInformation", "username=" + lastTriedUsername);
                    Log.e("LastLoginInformation", "password=" + lastTriedPassword);
                    tempDoNotUpdate = true;
                }*/
            } else if (m instanceof CreateSessionInfoResponseMessage) {
                Log.e(TAG + " handleResponse()",
                        "CreateSessionInfoResponseMessage");

                try {
                    CreateSessionInfoResponseMessage temp = (CreateSessionInfoResponseMessage) m;

                    Log.e(TAG + " handleResponse()", "calling putCreationKey()");
                    Toolbox.putCreationKey(temp);

                    UserInfo u = Toolbox.retrieveUserInfoByUsername(temp
                            .getUsername());

                    if (u != null) {

                        CreateSessionInvitationMessage newInvite=
                            new CreateSessionInvitationMessage(
                                    temp.getChatSessionID(),
                                    login_record
                                            .getUsername(),
                                    temp.getUsername(),
                                    login_record.getPublicKey().getModulus(),
                                    login_record.getPublicKey().getPublicExponent());

                           ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        ObjectOutputStream oos = new ObjectOutputStream(baos);
                        oos.writeObject(newInvite);
                        oos.flush();


                        Toolbox.storeUserKey(temp);

                        Log.e(TAG, "un encrypted size is "+ baos.toByteArray().length);

                        client.sendUDP(
                                new EncryptCreateSessionInvitationMessage(
                                        login_record.encryptothers(
                                                newInvite,
                                                temp.getPublic_modulus(),
                                                temp.getPublic_exponent())),
                               u.getUserIP());

                        System.out
                                .println("sending CreateSessionInvitationMessage");
                        System.out.println("my username: "
                                + login_record.getUsername());
                        System.out.println("invitee: " + temp.getUsername());
                        System.out.println("chat session id = "
                                + temp.getChatSessionID());

                        /*
                         * CreateSessionInvitationMessage invitation = new
                         * CreateSessionInvitationMessage(
                         * temp.getChatSessionID(), login_record.getUsername(),
                         * temp.getUsername()); client.sendUDP(invitation,
                         * u.getUserIP());
                         */
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else if (m instanceof InvitationAck) {

                System.out.println("received an InvitationAck");

                InvitationAck ack = (InvitationAck) m;

                synchronized (sentInvitations) {
                    Iterator<InvitationSendRecord> itr = sentInvitations
                            .iterator();
                    while (itr.hasNext()) {
                        InvitationSendRecord r = itr.next();

                        if (r.getMessage() instanceof CreateSessionInvitationMessage) {
                            if (r.chatSessionID()
                                    .equals(ack.getChatSessionID())) {
                                System.out
                                        .println("ack matched, removing invitation");
                                itr.remove();
                            }
                        }
                    }
                }

            } else if (m instanceof ResponseAck) {

                System.out.println("received an ResponseAck");

                ResponseAck ack = (ResponseAck) m;

                synchronized (sentInvitations) {
                    Iterator<InvitationSendRecord> itr = sentInvitations
                            .iterator();
                    while (itr.hasNext()) {
                        InvitationSendRecord r = itr.next();

                        if (r.getMessage() instanceof CreateSessionInvitationResponseMessage) {
                            if (r.chatSessionID()
                                    .equals(ack.getChatSessionID())) {
                                System.out
                                        .println("ack matched, removing invitation response");
                                itr.remove();
                            }
                        }
                    }
                }
            } else if (m instanceof ChatAck) {

                Log.e(TAG, "received an ChatAck");
                ChatAck ack = (ChatAck) m;
                boolean matchFound = false;
                
                synchronized (sentChatMessages) {
                    if (sentChatMessages.containsKey(ack.getChatSessionID())) {
                        ArrayList<ChatSendRecord> list = sentChatMessages
                                .get(ack.getChatSessionID());

                        Iterator<ChatSendRecord> itr = list.iterator();

                        int retransToThisGuy = 0; // number of retransmissions to this guy
                        ChatSessionMessage msg = null;   // needed to pass into addSystemMessageNowResponsive()
                        
                        
                        while (itr.hasNext()) {
                            ChatSendRecord r = itr.next();

                            if (r.getRecipientUsername().equals(
                                    ack.getAckUsername())) {
                            	retransToThisGuy++;
                            	if(r.getMessage().getSenderSeqNum() == ack
                                        .getSeqNum()){
                                	Log.e(TAG, "ChatAck match found");
                                	msg = r.getMessage();
                                	itr.remove();
                                	matchFound = true;
                                	retransToThisGuy--;
                            	}
                            }
                        }
                        
                        if(retransToThisGuy==0){
                        	ChatSessionInfo chInfo = Toolbox.getChatSession(ack.getChatSessionID());
                        	if(chInfo!=null){
                        		if(chInfo.isNotResponsiveParticipant(ack.getAckUsername()) 
	                        			&& msg!=null){
	                        		chInfo.removeParticipantNonResponsive(ack.getAckUsername());
	                        		chInfo.addSystemMessageNowResponsive(msg, ack.getAckUsername());
	                        		broadcastMessageReceived(msg);
	                        	}
                        	}
                        }
                    }
                    if (matchFound==false) {
                    	Log.e(TAG, "ChatAck match NOT found");
                    	Log.e(TAG, "chat session=" + ack.getChatSessionID());
                    	Log.e(TAG, "from=" + ack.getAckUsername());
                    	Log.e(TAG, "for=" + ack.getSeqNum());
                    }
                }
            } else if (m instanceof EncryptCreateSessionInvitationMessage) {

                // Log.d(TAG, "EncryptCreateSessionInvitationMessage");
                // try {
                CreateSessionInvitationMessage invitation = (CreateSessionInvitationMessage)

                login_record
                        .decrypt(((EncryptCreateSessionInvitationMessage) m)
                                .getInvitation());

                // UserInfo u =
                // Toolbox.retrieveUserInfo(temp.getSender_username());

                if (invitation.getSenderUsername() == null) {
                    System.out.println("getSender_username() is null");
                }

                /*
                 * if someone sent you CreateSessionInvitationMessage, then he
                 * should be your neighbor as well
                 */
                UserInfo u = Toolbox.retrieveUserInfoByUsername(invitation
                        .getSenderUsername());

                if (u == null) {
                    System.out.println("u is null");
                }

                if (invitation.getSenderUsername() != null && u != null) {

                    /*System.out.println("chat session id = "
                            + invitation.getChatSessionID());
                    boolean broadcastMade = (invitation);*/

                    synchronized (receivedInvitations) {
                        receivedInvitations.add(invitation);
                    }

                    CreateSessionInfoRequestMessageR askPubKey = new CreateSessionInfoRequestMessageR(
                            invitation.getChatSessionID(),
                            login_record.getLogin_session_id(),
                            invitation.getSenderUsername());
                    try {
                        Log.e("sending CreateSessionInfoRequestMessageR", "asking for user=" +
                                invitation.getSenderUsername());
                        client.sendTCP(askPubKey);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            } /*else if (m instanceof CreateSessionInfoResponseMessageR){

                CreateSessionInfoResponseMessageR keyInfo = (CreateSessionInfoResponseMessageR) m;

                Log.e("CreateSessionInfoResponseMessageR", "received key info about user=" +
                        keyInfo.getUsername());

                Toolbox.storeUserKey(keyInfo);

                synchronized (receivedInvitations) {
                    for(CreateSessionInvitationMessage invitation: receivedInvitations){
                        System.out.println("chat session id = "
                                + invitation.getChatSessionID());

                        if(invitation.getSenderUsername().equals(keyInfo.getUsername())){
                            System.out.println("chat session id = "
                                    + invitation.getChatSessionID());
                            boolean broadcastMade = broadcastChatInvitationNew(invitation);
                            Log.e("broadcastChatInvitation", "broadcastChatInvitation=" + broadcastMade);
                        }
                    }
                }

            }*/
            else if (m instanceof EncryptCreateSessionInvitationResponseMessage) {

                Log.d(TAG, "EncryptCreateSessionInvitationResponseMessage");
                // here we should create a session
                // this should be also put in some hash table,right?
                // try {
               EncryptCreateSessionInvitationResponseMessage enc = (EncryptCreateSessionInvitationResponseMessage) m;

               CreateSessionInvitationResponseMessage response =
                   (CreateSessionInvitationResponseMessage) login_record.decrypt(enc.getResponse());

                String chatSessionID = response.getChatSessionID();
                Log.e("handleResponse() CreateSessionInvitationResponseMessage",
                        "chatSessionID=" + chatSessionID);
                // BigInteger responseCode = response.getResponse();
                // int responseCode = response.getResponse();
                // ArrayList<BigInteger> pubKey =
                // Toolbox.getCreationKey(response.getChatSessionID());
                // [0] is exponent
                // [1] is modulus

                ChatSessionInfo chInfo = Toolbox.getChatSession(response
                        .getChatSessionID());
                UserInfo u = Toolbox.retrieveUserInfoByUsername(response
                        .getUsername());

                if (u != null && chInfo != null) {
                    Log.e("handleResponse() InvitationResponse",
                            " responder is not null ");

                    if (chInfo.getUsername() != null) {
                        // The username that I'm currently inviting
                        chInfo.setPendingForMyInvitation(false);

                        if (chInfo.getUsername().equals(response.getUsername())) {
                            // indeed this is the last guy I invited in this
                            // chat session

                            if (response.getResponse() == 1) {
                                // response is YES
                                System.out.println("got accepted");

                                // reset the last invitee to null
                                chInfo.setUsername(null);

                                // no longer pending b/c of my invitation
                                chInfo.setPendingForMyInvitation(false);

                                Toolbox.addParticipant(response.getUsername(),
                                        response.getChatSessionID(),
                                        u.getUserIP());

                                System.out.println("number of participants = "
                                        + chInfo.numberOfParticipants());

                                System.out.println(chInfo.getChatSessionName());

                                // broadcast to
                                // ChatWindowActivity/ChatSessionListActivity
                                // that the chat session is now no longer
                                // pending
                                broadcastInvitationResponse(response);
                                BigInteger modulus = Toolbox.getUserModulus(response.getUsername());
                                BigInteger exponent = Toolbox.getUserExponent(response.getUsername());
                                Toolbox.getChatSession(response.getChatSessionID()).generateSessionKey();
                                Key key = (Key) Toolbox.getChatSession(response.getChatSessionID()).getKey();
                                Cipher cipher = Toolbox.getChatSession(response.getChatSessionID()).getCipher();
                                Log.e("EncryptCreateSessionInvitationResponseMessage", "encrypting key");
                                BigInteger[] encKey= login_record.encryptothers(key, modulus, exponent);
                                //System.out.println(Arrays.toString(encKey));

                                Log.e("EncryptCreateSessionInvitationResponseMessage", "encrypting cipher");
                                BigInteger[] encCipher= login_record.encryptothers(cipher, modulus, exponent);
                                CreateSessionKeyMessage keymsg= new CreateSessionKeyMessage
                                (response.getChatSessionID(),encKey,encCipher);
                                client.sendUDP(keymsg, u.getUserIP());
                            } else if (response.getResponse() == 0) {
                                // response is NO
                                System.out.println("got rejected");
                                System.out.println("number of participants = "
                                        + chInfo.numberOfParticipants());

                                // reset the last invitee to null
                                chInfo.setUsername(null);
                                // no longer pending b/c of invitation
                                chInfo.setPendingForMyInvitation(false);

                                // broadcast to
                                // ChatWindowActivity/ChatSessionsListActivity
                                // that the chat session is now no longer
                                // pending
                                broadcastInvitationResponse(response);
                            }
                        }
                    }
                } else {
                    // what if u == null || chInfo == null?
                    // send ResponseAck
                    // ResponseAck ack = new ResponseAck(chInfo.getSessionid());
                    ResponseAck ack = new ResponseAck(
                            response.getChatSessionID());

                    try {
                        System.out.println("sending ACK for response received");
                        client.sendUDP(ack, u.getUserIP());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                System.out
                        .println("end of processing CreateSessionInvitationResponseMessage()");
                System.out.println(response.getResponse());

                /*
                 * client.sendUDP(new
                 * CreateSessionKeyMessage(response.getChatSessionID(),
                 * login_record.encryptothers( info, pubKey.get(1),
                 * pubKey.get(0))),u.getUserIP());
                 */

                /*
                 * } catch (IOException e) { e.printStackTrace(); }
                 */
            } else if (m instanceof CreateSessionInvitationResponseMessage) {

               Log.d(TAG, "CreateSessionInvitationResponseMessage");

               CreateSessionInvitationResponseMessage response =
                   (CreateSessionInvitationResponseMessage) m;

                String chatSessionID = response.getChatSessionID();
                Log.e("handleResponse() CreateSessionInvitationResponseMessage",
                        "chatSessionID=" + chatSessionID);

                ChatSessionInfo chInfo = Toolbox.getChatSession(response
                        .getChatSessionID());
                UserInfo u = Toolbox.retrieveUserInfoByUsername(response
                        .getUsername());

                if (u != null && chInfo != null) {
                    Log.e("handleResponse() InvitationResponse",
                            " responder is not null ");

                    // send ResponseAck
                    ResponseAck ack = new ResponseAck(chInfo.getSessionid());
                    try {
                        Log.e("handleResponse() InvitationResponse",
                                "sending ACK for response received");
                        client.sendUDP(ack, u.getUserIP());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                    if (chInfo.getUsername() != null) {
                        // The username that I'm currently inviting
                        chInfo.setPendingForMyInvitation(false);

                        if (chInfo.getUsername().equals(response.getUsername())) {
                            // indeed this is the last guy I invited in this
                            // chat session

                            if (response.getResponse() == 1) {
                                // response is YES
                                System.out.println("got accepted");

                                // reset the last invitee to null
                                chInfo.setUsername(null);

                                // no longer pending b/c of my invitation
                                chInfo.setPendingForMyInvitation(false);

                                Toolbox.addParticipant(response.getUsername(),
                                        response.getChatSessionID(),
                                        u.getUserIP());

                                System.out.println("number of participants = "
                                        + chInfo.numberOfParticipants());

                                System.out.println(chInfo.getChatSessionName());

                                // broadcast to
                                // ChatWindowActivity/ChatSessionListActivity
                                // that the chat session is now no longer
                                // pending
                                broadcastInvitationResponse(response);
                            } else if (response.getResponse() == 0) {
                                // response is NO
                                System.out.println("got rejected");
                                System.out.println("number of participants = "
                                        + chInfo.numberOfParticipants());

                                // reset the last invitee to null
                                chInfo.setUsername(null);
                                // no longer pending b/c of invitation
                                chInfo.setPendingForMyInvitation(false);

                                // broadcast to
                                // ChatWindowActivity/ChatSessionsListActivity
                                // that the chat session is now no longer
                                // pending
                                broadcastInvitationResponse(response);
                            }
                        }
                    }
                } else {
                    // what if u == null || chInfo == null?
                    // send ResponseAck
                    // ResponseAck ack = new ResponseAck(chInfo.getSessionid());
                    ResponseAck ack = new ResponseAck(
                            response.getChatSessionID());

                    try {
                        System.out.println("sending ACK for response received");
                        client.sendUDP(ack, u.getUserIP());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                System.out
                        .println("end of processing CreateSessionInvitationResponseMessage()");
                System.out.println(response.getResponse());

            } /*else if (m instanceof CreateSessionKeyMessage) {
                // Log.d(TAG, "CreateSessionKeyMessage");
                CreateSessionKeyMessage keyMsg = (CreateSessionKeyMessage)m;

                Log.e("Create Session Key Message", "got it!");

                ChatSessionInfo chInfo = Toolbox.getChatSession(keyMsg.getChatSessionID());

                Log.e("Create Session Key Message", "decrypting key");
                System.out.println(Arrays.toString(keyMsg.getEncKey()));
                Key key = (Key)login_record.decrypt(keyMsg.getEncKey());
                System.out.println(Arrays.toString(key.getEncoded()));
                chInfo.setKey(key);

                Log.e("Create Session Key Message", "decrypting cipher");
                Cipher cipher = (Cipher)login_record.decrypt(keyMsg.getEncCipher());
                chInfo.setCipher(cipher);

                System.out.println(key.toString());
            } else if (m instanceof GeneralChatMessage) {
                // Log.d(TAG, "GeneralChatMessage");}*/
            else if (m instanceof CreateSessionInvitationMessage) {
                Log.e(TAG + " handleResponse()",
                        "CreateSessionInvitationMessage");
                CreateSessionInvitationMessage invitation = (CreateSessionInvitationMessage) m;

                /*
                 * if someone sent you CreateSessionInvitationMessage, then he
                 * should be your neighbor as well
                 */
                UserInfo u = Toolbox.retrieveUserInfoByUsername(invitation
                        .getSenderUsername());

                if (u == null) {
                    System.out.println("u is null");
                    return;
                }

                if (Toolbox.getChatSession(invitation.getChatSessionID()) != null) {
                    // you are already in the chat session ---> ignore the
                    // invitation
                    System.out.println("already in the chat session");
                    // simply ack back
                    InvitationAck ack = new InvitationAck(
                            invitation.getChatSessionID());
                    System.out.println("replying with ACK");
                    try {
                        client.sendUDP(ack, u.getUserIP());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return;
                }

                if (invitation.getSenderUsername() != null && u != null) {

                    System.out.println("chat session id = "
                            + invitation.getChatSessionID());
                    boolean broadcastMade = broadcastChatInvitation(invitation);

                    if (broadcastMade) {
                        InvitationAck ack = new InvitationAck(
                                invitation.getChatSessionID());
                        System.out.println("replying with ACK");
                        try {
                            client.sendUDP(ack, u.getUserIP());
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } else if (m instanceof ChatSessionMessage) {
                Log.e(TAG, "ChatSessionMessage received");

                ChatSessionMessage msg = (ChatSessionMessage) m;

                ChatSessionInfo chInfo = Toolbox.getChatSession(msg
                        .getChatSessionID());
                UserInfo u = Toolbox.retrieveUserInfoByUsername(msg
                        .getSenderUsername());

                if (chInfo != null && chInfo.isDead()==false) {
                    System.out
                            .println("chat message for chat session (found): "
                                    + chInfo.getSessionid());
                    boolean nextMessage = chInfo.addNewMessageReceived(msg);

                    if (nextMessage == true) {
                        System.out.println("nextMessage" + nextMessage + " "
                                + msg.getSenderSeqNum());

                        // ack the sender
                        ChatAck ack = new ChatAck(msg.getChatSessionID(),
                                Toolbox.getMyInfo().getUsername(),
                                msg.getSenderSeqNum());
                        
                        boolean ackSent = true;
                        try {
                            client.sendUDP(ack, u.getUserIP());
                        } catch (IOException e) {
                            e.printStackTrace();
                            ackSent = false;
                        }
                        if(ackSent){
                        	System.out.println("ChatAck to " + u.getUsername() 
                            		+ " " + u.getUserIP() + " sent");
                        }
                        
                        broadcastMessageReceived(msg);
                    } else {
                        // do not ack the sender
                        System.out.println("nextMessage" + nextMessage + " "
                                + msg.getSenderSeqNum());
                    }
                } else {
                    System.out.println("chat message for chat session (not found): "
                                    + msg.getChatSessionID());
                }
            } /*else if (m instanceof ReportNeighborMessage) {
                Log.e(TAG + "handleResponse()", "ReportNeighborMessage");
                ReportNeighborMessage nbrmsg = (ReportNeighborMessage) m;

                ArrayList<NeighborInfo> nbrinfo = nbrmsg.getReportNbrList();

                ChatSessionInfo session = Toolbox.getChatSession(nbrmsg
                        .getChatSessionID());

                if (session == null) {
                    Log.e(TAG + "handleResponse()", nbrmsg.getChatSessionID()
                            + " does not exist ");
                    return;
                }

                for (NeighborInfo hisNbrParticipant : nbrinfo) {
                    if (session.hasParticipant(hisNbrParticipant.getUsername())) {
                        Log.e(TAG + "handleResponse()",
                                "calling increasettl on "
                                        + hisNbrParticipant.getUsername());
                        session.increasettl(hisNbrParticipant.getUsername());
                    } else {
                        if (hisNbrParticipant.getUsername().equals(
                                login_record.getUsername())) {
                            // this guy reports me as his neighbor,
                            // that means I should call increasettl() on him for
                            // this chat session
                            Log.e(TAG + "handleResponse()",
                                    hisNbrParticipant.getUsername()
                                            + ", that's me being reported by "
                                            + nbrmsg.getReporter());
                            session.increasettl(nbrmsg.getReporter());
                        } else {
                            Log.e(TAG + "handleResponse()",
                                    hisNbrParticipant.getUsername()
                                            + ", NEW PARTICIPANT?!");
                            session.addParticipant(
                                    hisNbrParticipant.getUsername(),
                                    Toolbox.intToIp(hisNbrParticipant.getIP()));
                            Log.e(TAG + "handleResponse()",
                                    hisNbrParticipant.getUsername() + ", added");
                            // got to broadcast to ChatWindowActivity and
                            // ChatSessionListActivity
                            // that the participant list has changed
                            broadcastChangeInChatSession(session);
                        }
                    }
                }
            }*/
            else if(m instanceof ReportKeepAliveMessage) {
                //Log.e(TAG+"handleResponse()", "ReportKeepAliveMessage");
                ReportKeepAliveMessage kl = (ReportKeepAliveMessage)m;

                String reporter = kl.getReporter();

                ChatSessionInfo session=
                    Toolbox.getChatSession(kl.getChatSessionID());

                if(session ==null) {
                    /*Log.e(TAG+"handleResponse()",
                            kl.getChatSessionID() + " does not exist ");*/
                    return;
                } else {
                    // I received a keep alive
                    //  first thing to check is "is the forwarder my neighbor"
                    String forwarder = kl.getForwarder();
                    UserInfo forwardUser = Toolbox.retrieveUserInfoByUsername(forwarder);
                    int reporterRound = kl.getKLRound();

                    if(forwardUser!=null && forwardUser.isANeighbor()){
                        if ( session.hasParticipant(reporter) ) {
                            if(reporterRound > session.getParticipantLatestKLRound(reporter)){
                                Log.e(TAG+"handleResponse()", "calling increasettl on "
                                    + reporter + " for chat " + session.getSessionid());
                                if(kl.isDeKL()==false){
                                	session.increasettl(reporter);
                                }else{
                                	session.killttl(reporter);
                                }
                                session.setParticipantIP(reporter, kl.getReporterIP());
                                session.setParticipantLatestKLRound(reporter, reporterRound);

                                // my job is then to forward this KL to all my neighboring participants
                                forwardToNeighborsInChatSession(kl);
                                
                                // could have been unresponsive
                                if(session.isNotResponsiveParticipant(reporter)){
                                	Log.e(TAG, reporter + " is not responsive in chat " 
                                			+ session.getSessionid());
                                	synchronized(sentChatMessages){
                                		if (sentChatMessages.containsKey(session
                                                .getSessionid())) {
                                            ArrayList<ChatSendRecord> record = sentChatMessages.get(session
                                                .getSessionid());
                                            Iterator<ChatSendRecord> itr = record.iterator();
                                            while(itr.hasNext()){
                                            	ChatSendRecord r = itr.next();
                                            	boolean reset = false;
                                            	if(r.getRecipientUsername().equals(reporter)){
                                            		r.resetSendCount();
                                            		r.setIP(kl.getReporterIP());
                                            		reset = true;
                                            	}
                                            	if(reset){
                                            		Log.e("chat session " + session
                                                            .getSessionid(), 
                                                            " to " + reporter +
                                                            " retransmission restarted again");
                                            	}
                                            }
                                        }
                                	}
                                }
                            }
                        } else {
                            Log.e(TAG+"handleResponse()", reporter
                                    + ", NEW PARTICIPANT?!");

                            session.addParticipant(reporter,
                                    Toolbox.intToIp(kl.getReporterIP()));

                            session.setParticipantLatestKLRound(kl.getReporter(), reporterRound);

                            /*Log.e(TAG + "handleResponse()", reporter
                                    + ", added");*/

                            UserInfo u = Toolbox.retrieveUserInfoByUsername(reporter);

                            if(u==null){
                                UserInfo newUser = new UserInfo(reporter, kl.getReporter());
                                newUser.setUserIP(Toolbox.intToIp(kl.getReporterIP()));
                                Toolbox.addNewUser(newUser);
                                Resources res = getResources();
                                Drawable myImage = res.getDrawable(R.drawable.no_picture);
                                newUser.setProfilePicture(myImage);
                                Log.e("New User", reporter + " " + kl.getReporterIP());
                            }

                            // my job is then to forward this KL to all my neighboring participants
                            forwardToNeighborsInChatSession(kl);

                            broadcastChangeInChatSession(session);
                        }
                    }
                }
            }
        }

        private void forwardToNeighborsInChatSession(ReportKeepAliveMessage kl) {
            ChatSessionInfo session = Toolbox.getChatSession(kl
                    .getChatSessionID());
            String forwarder = kl.getForwarder();

            // figure out which participant is my neighbor
            for (String username : session.getParticipants().keySet()) {
                UserInfo u = Toolbox.retrieveUserInfoByUsername(username);

                // no need to forward to reporter or forwarder
                if (u.isANeighbor()
                        && !u.getUsername().equals(kl.getReporter())
                        && !u.getUsername().equals(forwarder)) {
                    // send the same exact keep alive report out
                    // besides setting the forwarder to myself
                    UserInfo me = Toolbox.getMyInfo();
                    if(me==null){
                        Log.e("I'm logged out", "I'm logged out");
                        return;
                    }
                    kl.setForwarder(me.getUsername());
                    try {
                        client.sendUDP(kl, u.getUserIP());
                        System.out.println("forward keepalive for "
                                + kl.getReporter() + ", receiver is "
                                + username + ", round is " + kl.getKLRound()
                                + ", forwarder is " + kl.getForwarder());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        /**
         * increment 'updateRoundNumber' send location update message to server
         * with new round number update 'lastUpdateTime'
         *
         * note that 'locationReportMode' affects what location will be updated
         * to the server
         */
        private void sendLocationUpdateMessage() {
            boolean sent = true;

            if (locationReportMode == CMU_MODE) {
                lastKnownLocation.setLongitude(cmu_longitude);
                lastKnownLocation.setLatitude(cmu_latitude);
            } else if (locationReportMode == WATERFRONT_MODE) {
                lastKnownLocation.setLongitude(waterfront_longitude);
                lastKnownLocation.setLatitude(waterfront_latitude);
            } // else in REAL_MODE, we don't manually set the values

            ClientUpdateLocationMessage locationMessage = new ClientUpdateLocationMessage(
                    lastKnownLocation.getLatitude(),
                    lastKnownLocation.getLongitude(),
                    login_record.getLogin_session_id());

            locationMessage.setRound_number(++updateRoundNumber);
            lastUpdateTime = System.currentTimeMillis();
            try {
                client.sendTCP(locationMessage);
            } catch (IOException e) {
                e.printStackTrace();
                sent = false;
            }
            if (DEBUG && sent)
                System.out.println("LocationUpdateMessageSent with round=" + locationMessage.getRound_number());
        }

        /*
         * send broadcast for ClientLoginResponseMessage received the intended
         * receiver of this broadcast is LoginActivity
         */
        private void broadcastLoginResponse(ClientLoginResponseMessage mm) {
            Intent intentLogin = new Intent(
                    StringConstants.ACTION_FOR_LOGIN_ACTIVITY);
            intentLogin.putExtra(StringConstants.EXTRA_USERNAME, mm.getUsername());
            intentLogin.putExtra(StringConstants.EXTRA_RESPONSE, mm.getResponse());
            intentLogin.putExtra(StringConstants.EXTRA_LOGIN_SESSION_ID,
                    mm.getLoginSessionID());
            //intentLogin.putExtra(StringConstants.EXTRA_PICUTRE, mm.getBytes());
            //intentLogin.putExtra(StringConstants.EXTRA_BG, mm.getBG());

            Log.d(TAG, " broadcastLoginResponse() is called ");
            if(mm.getResponse() == ClientLoginResponseMessage.LOGIN_SUCCESSFUL){
            	UserInfo myInfo = new UserInfo(mm.getUsername(), mm.getNickname());
            	Log.e("broadcastLoginResponse picture", ""+myInfo.getProfilePicture());
            	myInfo.setLoginSessionId(mm.getLoginSessionID());
            	myInfo.setMessageBackgroundColor(MessageBackgroundColor.values()[mm.getBG()]);
            	if(mm.getBytes()!=null){
                    // generate Drawable from byte[]
                    Drawable image = new BitmapDrawable(
                            BitmapFactory.decodeByteArray(mm.getBytes(), 0, mm.getBytes().length)
                        );
                    myInfo.setProfilePicture(image);
                }
                Toolbox.setMyInfo(myInfo);
            }
            
            sendBroadcast(intentLogin);

            Intent intentRegister = new Intent(
                    StringConstants.ACTION_FOR_REGISTER_ACTIVITY);
            intentRegister.putExtra(StringConstants.EXTRA_USERNAME, mm.getUsername());
            intentRegister.putExtra(StringConstants.EXTRA_RESPONSE, mm.getResponse());
            sendBroadcast(intentRegister);
        }

        /*
         * send broadcast for ClientRegisterResponseMessage received the
         * intended receiver of this broadcast is RegisterActivity
         */
        private void broadcastRegisterResponse(ClientRegisterResponseMessage mm) {
            Intent intent = new Intent(
                    StringConstants.ACTION_FOR_REGISTER_ACTIVITY);
            intent.putExtra(StringConstants.EXTRA_USERNAME, mm.getUsername());
            intent.putExtra(StringConstants.EXTRA_RESPONSE, mm.getResponse());
            Log.d(TAG, " broadcastRegisterResponse() is called ");
            sendBroadcast(intent);
        }

        /*
         * send broadcast for LogoutCommandFromServer received the intended
         * receiver of this broadcast is DroidChatTabActivity and
         * ChatWindowActivity
         */
        private void broadcastLogoutCommand() {
            Log.e(TAG, "broadcastLogoutCommand");

            // broadcast to DroidChatTabActivity
            Intent intentDroidChatTab = new Intent(
                    StringConstants.ACTION_CHAT_INVITATION);
            intentDroidChatTab.putExtra("logout", "logout");
            sendBroadcast(intentDroidChatTab);

            Intent intentChatWindow = new Intent(
                    StringConstants.ACTION_FOR_CHAT_WINDOW_ACTIVITY);
            intentChatWindow.putExtra("logout", "logout");
            sendBroadcast(intentChatWindow);

            // broadcast to ChatWindowActivity
            Toolbox.setMyInfo(null);
            login_record = null; // got to do this, in case you keep updating
            // location
            // with your old login_session_id
        }

        /*
         * send broadcast for CreateSessionInvitationMessage received. The
         * intended receiver of this broadcast is DroidChatTabActivity
         *
         * boolean return value tells you an actual broadcast was made or not
         */
        private boolean broadcastChatInvitation(
                CreateSessionInvitationMessage invitation) {
            Log.e(TAG + "broadcastChatInvitation()", "broadcastChatInvitation");

            boolean receivedAwaitingUserReaction = false;

            synchronized (receivedInvitations) {
                System.out.println("receivedInvitations size="
                        + receivedInvitations.size());

                Iterator<CreateSessionInvitationMessage> itr = receivedInvitations
                        .iterator();
                while (itr.hasNext()) {
                    CreateSessionInvitationMessage m = itr.next();

                    if (m == null) {
                        System.out.println("m is null");
                    } else {
                        System.out.println(m.getChatSessionID());
                        System.out.println(m.getSenderUsername());
                    }
                    if (invitation.equals(m)) {
                        receivedAwaitingUserReaction = true;
                    }

                }
                if (receivedAwaitingUserReaction == false) {
                    receivedInvitations.add(invitation);
                }
            }

            if (receivedAwaitingUserReaction == true) {
                // duplicated invitation
            	Log.e(TAG, "duplicated invitation");
                return false;
            }

            boolean returnValue = NetworkService.this.broadcastChatInvitation(
                    invitation, NetworkService.this);

            if (receivedAwaitingUserReaction == false && returnValue == false) {
                // remove the invitation from receivedInvitations
                // since user did not get to see pop up
                synchronized (receivedInvitations) {
                    Iterator<CreateSessionInvitationMessage> itr = receivedInvitations
                            .iterator();
                    while (itr.hasNext()) {
                        CreateSessionInvitationMessage m = itr.next();
                        if (invitation.equals(m)) {
                            itr.remove();
                        }
                    }
                }
            }
            return returnValue;
        }

        /*
         * send broadcast to ChatWindowActivity/ChatSessionListActivity because
         * some chat message has been received
         */
        private void broadcastMessageReceived(ChatSessionMessage msg) {
            Log.e(TAG, "broadcastMessageReceived");
            Intent intentChatWindow = new Intent(
                    StringConstants.ACTION_FOR_CHAT_WINDOW_ACTIVITY);

            intentChatWindow.putExtra(StringConstants.EXTRA_CHAT_SESSION_ID,
                    msg.getChatSessionID());
            intentChatWindow.putExtra(StringConstants.EXTRA_CHAT_MESSAGE, true);
            sendBroadcast(intentChatWindow);

            Intent intentChatSessionList = new Intent(
                    StringConstants.ACTION_FOR_CHAT_SESSION_LIST_ACTIVITY);
            intentChatSessionList.putExtra(
                    StringConstants.EXTRA_CHAT_SESSION_ID,
                    msg.getChatSessionID());
            sendBroadcast(intentChatSessionList);
        }

        private void broadcastChangeInChatSession(ChatSessionInfo chInfo) {
            Log.e(TAG, "broadcastChangeInChatSession");

            Intent intentChatWindow = new Intent(
                    StringConstants.ACTION_FOR_CHAT_WINDOW_ACTIVITY);
            intentChatWindow.putExtra(StringConstants.EXTRA_CHAT_SESSION_ID,
                    chInfo.getSessionid());
            intentChatWindow.putExtra(StringConstants.EXTRA_REFRESH_LIST, true);
            sendBroadcast(intentChatWindow);

            Intent intentChatSessionList = new Intent(
                    StringConstants.ACTION_FOR_CHAT_SESSION_LIST_ACTIVITY);
            intentChatSessionList.putExtra(
                    StringConstants.EXTRA_CHAT_SESSION_ID,
                    chInfo.getSessionid());
            intentChatSessionList.putExtra(StringConstants.EXTRA_REFRESH_LIST,
                    true);
            sendBroadcast(intentChatSessionList);
        }

        /**
         * send broadcast to ChatWindowActivity because some pending chat
         * session is now no longer pending
         */
        private void broadcastInvitationResponse(
                CreateSessionInvitationResponseMessage response) {

            Log.e(TAG, "broadcastInvitationResponse");

            Intent intentChatWindow = new Intent(
                    StringConstants.ACTION_FOR_CHAT_WINDOW_ACTIVITY);
            intentChatWindow.putExtra(
                    StringConstants.EXTRA_IS_INVITATION_RESPONSE, true);
            intentChatWindow.putExtra(
                    StringConstants.EXTRA_IS_INVITATION_ACCEPTED,
                    response.getResponse() == 1 ? true : false);
            intentChatWindow.putExtra(StringConstants.EXTRA_CHAT_SESSION_ID,
                    response.getChatSessionID());

            sendBroadcast(intentChatWindow);

            Intent intentChatSessionList = new Intent(
                    StringConstants.ACTION_FOR_CHAT_SESSION_LIST_ACTIVITY);
            intentChatSessionList.putExtra(
                    StringConstants.EXTRA_CHAT_SESSION_ID,
                    response.getChatSessionID());
            intentChatSessionList.putExtra(StringConstants.EXTRA_REFRESH_LIST,
                    true);
            sendBroadcast(intentChatSessionList);
        }

        /**
         * send broadcast for NeighborInfoMessage received intended receiver of
         * this broadcast is NearbyUsersListActivity
         */
        private void broadcastNeighborInfo(NeighborInfoMessage mm) {
            Intent intent = new Intent(
                    StringConstants.ACTION_NEIGHBOR_INFO_MESSAGE);
            intent.putExtra(StringConstants.EXTRA_USERNAME, mm.getUsername());
            intent.putExtra(StringConstants.EXTRA_USER_IP, mm.getUserIp());
            // intent.putExtra(StringConstants.EXTRA_ROUND_NUMBER,
            // mm.getRound());
            Log.d(TAG, " broadcastNeighborInfo() is called ");

            if (mm.getRound() == updateRoundNumber) {
                UserInfo u = Toolbox.retrieveUserInfoByUsername(mm
                        .getUsername());

                if (u == null) {
                    u = new UserInfo(mm.getUsername(), mm.getUsername());
                    u.setUserIP(mm.getUserIp());
                    u.setRound(mm.getRound());
                    u.setNeighbor(true);
                    u.setNickName(mm.getNickname());
                    u.setStatusMessage("");

                    if(mm.getBytes()!=null){
                        // generate Drawable from byte[]
                        Drawable image = new BitmapDrawable(
                            BitmapFactory.decodeByteArray(mm.getBytes(), 0, mm.getBytes().length)
                            );
                        u.setProfilePicture(image);
                    }
                    u.setMessageBackgroundColor(MessageBackgroundColor.values()[mm.getBG()]);
                    Toolbox.addNewUser(u);
                } else {
                    // just update the round number
                    u.setRound(mm.getRound());
                    u.setUserIP(mm.getUserIp());
                    u.setNeighbor(true);
                }
            }

            sendBroadcast(intent);
        }

        /**
         * send broadcast when hashtable 'userInfoHashtable' in ToolBox has been
         * changed
         *
         * intended receiver of this broadcast is NearbyUsersListActivity
         */
        private void broadcastUpdateNeighborList() {
            Intent intent = new Intent(
                    StringConstants.ACTION_NEIGHBOR_INFO_MESSAGE);
            Log.d(TAG, " broadcastUpdateNeighborList() is called ");
            sendBroadcast(intent);
        }

        private Selector initSelector() throws IOException {
            // Create a new selector
            Selector socketSelector = SelectorProvider.provider()
                    .openSelector();
            return socketSelector;
        }

        private boolean finishConnection(SelectionKey key) throws IOException {
            SocketChannel socketChannel = (SocketChannel) key.channel();

            boolean connected = false;

            // Finish the connection. If the connection operation failed
            // this will raise an IOException.
            try {
                connected = socketChannel.finishConnect();
            } catch (IOException e) {
                // Cancel the channel's registration with our selector
                key.cancel();
            }

            return connected;
        }

        /************* SSL related methods *************************/
        private boolean finishConnectionSSL(SelectionKey key, String ip,
                int port) throws IOException {
            SocketChannel socketChannel = (SocketChannel) key.channel();

            // Finish the connection. If the connection operation failed
            // this will raise an IOException.
            try {
                socketChannel.finishConnect();
            } catch (IOException e) {
                // Cancel the channel's registration with our selector
                key.cancel();
                return false;
            }

            Socket socket = socketChannel.socket();
            registerSocketSSL(socket, ip, port, true);
            return true;
        }

        private void registerSocketSSL(Socket socket, String host, int port,
                boolean client) throws IOException {
            // "Upgrade" the new socket to an SSLSocket
            SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory
                    .getDefault();
            SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,
                    host, port, true);

            Log.d("registerSocket. host", host);
            Log.d("registerSocket. port", "" + port);

            // Ensure that when we start reading on this socket it
            // acts the part of the server during the SSL handshake.
            sslSocket.setUseClientMode(client);
            serverSSLSocket = sslSocket;
            Log.d("serverSSLSocket", "" + serverSSLSocket);
        }

        /**
         * return false means the sslSession is not established return true
         * means the sslSession is established
         *
         * @throws SSLException
         */
        private boolean configureSSLSocket(Socket socket, SSLSocket sslSocket)
                throws SSLException {
            int sslHandshakeTimeout = 30000;

            if (serverSSLSession == null) {
                // no SSL Session established with server yet
                Log.d(TAG, "no SSL Session established with server yet");

                try {
                    sslSocket.setSoTimeout(sslHandshakeTimeout);
                } catch (SocketException e) {
                    e.printStackTrace();
                }

                // Get the SSL session. This forces a handshake and is used
                // to indicate that we've performed the handshake for this
                // SSLSocket.
                Log.d(TAG, "calling getSession()");
                SSLSession session = sslSocket.getSession();
                Log.d(TAG, "done calling getSession()");

                serverSSLSession = session;
                if (session.getId() != null && session.getId().length != 0) {
                    Log.d("SSL session details", "" + session);
                    Log.d(TAG, "valid");
                } else {
                    Log.d(TAG, "invalid");
                    if (sslSocket.getUseClientMode()) {
                        throw new SSLException(
                                "SSL session handshake failed (is the server SSL enabled?)");
                    }
                }
            }
            try {
                sslSocket.setSoTimeout(500);
            } catch (SocketException e) {
                e.printStackTrace();
            }
            return true;
        }

        private int readTCPSSL(SelectionKey key) throws IOException {
            Log.d(TAG, "readTCPSSL()");

            SocketChannel socketChannel = (SocketChannel) key.channel();

            key.cancel();
            key.channel().configureBlocking(true);

            try {
                configureSSLSocket(socketChannel.socket(), serverSSLSocket);
            } catch (SSLException e) {
                // SSL handshake fails
                Log.e(TAG, "SSL handshake with server fails");
                return -1;
            }

            InputStream is = serverSSLSocket.getInputStream();
            int numRead;

            if (pendingIncomingData.isEmpty()) {
                pendingIncomingData.add(ByteBuffer.allocate(4)); // [0] is the
                // byteSize
                // of the
                // message
                byte[] header = new byte[4];

                try {
                    numRead = is.read(header, 0, header.length);
                } catch (SocketTimeoutException e) {
                    // The read timed out so we're done.
                    numRead = 0;
                } catch (IOException e) {
                    Log.e("IOException ", "" + e);
                    return -1;
                }
                Log.d(TAG, "numRead=" + numRead);
                if (numRead == -1) {
                    return -1;
                } else {
                    pendingIncomingData.get(0).put(header, 0, numRead);
                    if (pendingIncomingData.get(0).remaining() == 0) {
                        pendingIncomingData.get(0).flip();
                        int byteSize = pendingIncomingData.get(0).getInt();
                        pendingIncomingData.add(ByteBuffer.allocate(byteSize)); // [1]
                        // is
                        // the
                        // actual
                        // message
                    }
                }
            } else {
                ByteBuffer byteSizeBuf = pendingIncomingData.get(0);
                int remaining = byteSizeBuf.remaining();
                if (remaining == 0) {
                    ByteBuffer actualMsg = pendingIncomingData.get(1);
                    byte[] dataBuf = new byte[actualMsg.remaining()];
                    try {
                        numRead = is.read(dataBuf, 0, dataBuf.length);
                    } catch (SocketTimeoutException e) {
                        // The read timed out so we're done.
                        numRead = 0;
                    } catch (IOException e) {
                        Log.e("IOException ", "" + e);
                        // when this happens, we want to close the socket and
                        // try connecting to the next server
                        return -1;
                    }
                    Log.d(TAG, "numRead=" + numRead);
                    if (numRead == -1) {
                        return -1;
                    } else {
                        actualMsg.put(dataBuf, 0, numRead);
                        if (actualMsg.remaining() == 0) {
                            ByteArrayInputStream bais = new ByteArrayInputStream(
                                    actualMsg.array());
                            ObjectInputStream ois = new ObjectInputStream(bais);
                            DroidChatMessage m = null;
                            try {
                                m = (DroidChatMessage) ois.readObject();
                            } catch (ClassNotFoundException e) {
                                e.printStackTrace();
                            }
                            // Hand the data off to our worker thread
                            pendingIncomingData.clear();
                            try {
                                handleResponse(m);
                            } catch (NoSuchPaddingException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                        }
                    }
                } else {
                    byte[] remainingMessageHeader = new byte[remaining];
                    try {
                        numRead = is.read(remainingMessageHeader, 0,
                                remainingMessageHeader.length);
                    } catch (SocketTimeoutException e) {
                        // The read timed out so we're done.
                        numRead = 0;
                    } catch (IOException e) {
                        // when this happens, we want to close the socket and
                        // try connecting to the next server
                        Log.e("IOException ", "" + e);
                        return -1;
                    }
                    Log.d(TAG, "numRead=" + numRead);
                    if (numRead == -1) {
                        return -1;
                    } else {
                        pendingIncomingData.get(0).put(remainingMessageHeader,
                                0, numRead);
                        if (pendingIncomingData.get(0).remaining() == 0) {
                            pendingIncomingData.get(0).flip();
                            int byteSize = pendingIncomingData.get(0).getInt();
                            pendingIncomingData.add(ByteBuffer
                                    .allocate(byteSize)); // [1] is the actual
                            // message
                        }
                    }
                }
            }

            // if the method has not returned up to this point, then we are
            // still interested in READING
            key.channel().configureBlocking(false);

            // Queue a channel reregistration
            Log.d(TAG, "Queue a channel reregistration");
            synchronized (pendingChanges) {
                pendingChanges.add(new ChangeRequest(socketChannel,
                        ChangeRequest.REREGISTER, SelectionKey.OP_READ));
            }
            return numRead;
        }

        private int writeTCPSSL(SelectionKey key) {
            SocketChannel socketChannel = (SocketChannel) key.channel();
            Socket socket = socketChannel.socket();

            try {
                key.cancel();
                key.channel().configureBlocking(true);

                try {
                    configureSSLSocket(socket, serverSSLSocket);
                } catch (SSLException e) {
                    Log.e(TAG, "SSL handshake with server fails");
                    return -1;
                }

                OutputStream os = serverSSLSocket.getOutputStream();

                // Write until there's no more data ...
                while (!pendingDataQueue.isEmpty()) {
                    ByteBuffer buf = pendingDataQueue.get(0);
                    // System.out.println(" writing " + buf.capacity());
                    os.write(buf.array());
                    // System.out.println(" removing ");
                    pendingDataQueue.remove(0);
                }

                // We've written the data, reregister the channel
                key.channel().configureBlocking(false);

                Log.d(TAG, "Queue a channel reregistration");

                synchronized (pendingChanges) {
                    pendingChanges.add(new ChangeRequest(socketChannel,
                            ChangeRequest.REREGISTER, SelectionKey.OP_READ));
                }

            } catch (IOException e) {
                e.printStackTrace();
                return -1;
            }

            return 1;
        }

        /************* end of SSL related methods *************************/
    }

    public boolean broadcastChatInvitation(
            CreateSessionInvitationMessage invitation, Context context) {

        // Log.d(TAG, "list size=" + Toolbox.getServiceUserListSize());
        // receivedInvitations.add(null);

        Log.e(TAG + " broadcastChatInvitation()", invitation.getChatSessionID());
        Log.e(TAG + " broadcastChatInvitation()",
                invitation.getSenderUsername());
        
        String chatSessionID;
        
        if(invitation.isOneOnone()){
        	Log.e(TAG + " ()", "CALLING addNewOneOnOneChatSessionInfoByUsername()");
        	chatSessionID = Toolbox.addNewOneOnOneChatSessionInfoByUsername(
                invitation.getSenderUsername(), invitation.getChatSessionID());
        } else {
        	Log.e(TAG + " ()", "CALLING addNewMultiUserChatSessionInfo()");
        	chatSessionID = Toolbox.addNewMultiUserChatSessionInfo(
                    invitation.getSenderUsername(),
                    invitation.getChatSessionID(),
                    invitation.getExistingParticipants(),
                    invitation.getT());
        }
        
        ChatSessionInfo chInfo = Toolbox.getChatSession(chatSessionID);

        if (chInfo.isBrandNew() == false) {
            Log.e(TAG + " ()", "not a new session");
            return false;
        }

        Log.e(TAG + " ()", chatSessionID);

        if (Toolbox.getServiceUserListSize() == 0) {
            // add to notification bar
            NotificationManager mNotificationManager = (NotificationManager) context
                    .getSystemService(Context.NOTIFICATION_SERVICE);
            int icon = R.drawable.notification_icon;
            CharSequence tickerText;
            if (receivedInvitations.size() == 1) {
                tickerText = this.getText(R.string.new_chat_invitation);
            } else {
                tickerText = ""
                        + receivedInvitations.size()
                        + this.getText(R.string.new_chat_invitations);
            }
            long when = System.currentTimeMillis();

            Notification notification = new Notification(icon, tickerText, when);
            notification.flags |= Notification.FLAG_AUTO_CANCEL;

            // Define the Notification's expanded message and Intent:
            CharSequence contentTitle = this.getResources().getText(
                    R.string.app_name);
            CharSequence contentText;
            if (receivedInvitations.size() == 1) {
                contentText = this.getResources().getText(
                        R.string.new_invitation_from)
                        + Toolbox.getMyInfo().getUsername();
            } else {
                contentText = tickerText;
            }
            Intent notificationIntent = new Intent(context,
                    DroidChatTabActivity.class);
            notificationIntent.putExtra(StringConstants.EXTRA_IS_INVITED, true);
            PendingIntent contentIntent = PendingIntent.getActivity(context, 0,
                    notificationIntent, 0);

            notification.setLatestEventInfo(context, contentTitle, contentText,
                    contentIntent);
            // notification.number = receivedInvitations.size();
            // Pass the Notification to the NotificationManager:
            final int HELLO_ID = 1;
            mNotificationManager.notify(HELLO_ID, notification);
        } else {
            // just let the activity pop up window
            Log.e(TAG,
                    "activities in list: " + Toolbox.getServiceUserListString());
            // just send a broadcast.
            String invitor = invitation.getSenderUsername();
            Intent intent = new Intent(StringConstants.ACTION_CHAT_INVITATION);
            intent.putExtra(StringConstants.EXTRA_INVITOR_USERNAME, invitor);
            intent.putExtra(StringConstants.EXTRA_CHAT_SESSION_ID,
                    invitation.getChatSessionID());
            intent.putExtra(StringConstants.EXTRA_1ON1, invitation.isOneOnone());

            if (invitation.isOneOnone() == false) {
                // a multi-user chat invitation
                intent.putExtra(StringConstants.EXTRA_EXISTING_PARTICIPANTS,
                        Toolbox.formatExistingParticipants(invitation
                                .getExistingParticipants().keySet()));
                intent.putExtra(StringConstants.EXTRA_EXISTING_PARTICIPANTS_IP,
                        Toolbox.formatExistingParticipantsIP(invitation
                                .getExistingParticipants()));
                Log.e(TAG + " invitation isOneOnone", "is false");
            } else {
                Log.e(TAG + " invitation isOneOnone", "is true");
            }

            sendBroadcast(intent);
        }
        return true;
    }
}
